C++无效的对象返回语义

C++ invalid object return semantics

本文关键字:返回 语义 对象 无效 C++      更新时间:2023-10-16

在任何类似的命名问题中都找不到答案。

我希望用户能够在对象生命周期的任何时候初始化字符串成员,不一定是在构造时,但我希望他们知道,在初始化字符串之前,对象是无效的。。。

创建一个简单的类时,请说出以下内容:

#include <string>
class my_class {
  public:
    my_class() : _my_str() { }
    my_class(const std::string & str) : my_class() {
      set_my_str(str);
    }
    std::string get_my_str() const {
      return _my_str;
    }
    void set_my_str(const std::string & str) {
      _my_str = str;
    }
  private:
    std::string _my_str;
};

如果用户创建了一个类的空实例(即使用空构造函数),_my_str将是一个空的/未初始化的字符串?

因此,我看到了两种处理行为的方式:上面提到的返回空字符串的方式,或者可能的第二种方式:

#include <string>
class my_class {
  public:
    my_class() : _my_str(), _my_str_ptr(nullptr) { }
    my_class(const std::string & str) : my_class() {
      set_my_str(str);
    }
    std::string * get_my_str() const {
      return _my_str_ptr;
    }
    void set_my_str(const std::string & str) {
      _my_str = str;
      _my_str_ptr = &_my_str;
    }
  private:
    std::string _my_str;
    std::string * _my_str_ptr;
};

在哪里返回nullptr,并维护指向局部变量的指针?

这种行为有效吗?首选哪种方式,为什么?第二种方法不是更好吗?因为你告诉用户,"听着,这个对象当前无效,你需要初始化它",同时仍然暗示你正在管理这个对象的生存期。

_my_str将是一个空的/未初始化的字符串?

空的,是的。未初始化,否。它已完全初始化(为空字符串)。

在哪里返回nullptr,并维护指向局部变量的指针?

这种行为有效吗?

是的,它是有效的,但是

首选哪种方式,为什么?第二种方式不是更好吗?因为你告诉用户;听着,这个对象当前无效,你需要初始化它"同时仍然暗示您正在管理这样的对象的生存期。

为此维护两个不同的成员变量是完全没有意义的。听起来您需要的是std::optional(或Boost中的等效boost::optional),因此_my_str有两种状态:空/无效(不包含字符串)和非空/有效(包含字符串):

#include <string>
#include <experimental/optional>
using std::experimental::optional;
class my_class {
  public:
    my_class() /* default-initializes _my_str as empty */ { }
    my_class(const std::string & str) : _my_str(str) { }
    const std::string * get_my_str() const {
      if (_my_str) // if it exists
        return &*_my_str; // return the string inside the optional
      else
        return nullptr; // if the optional is empty, return null
    }
    /* Or simply this, if you don't mind exposing a bit of the
       implementation details of the class:
    const optional<std::string> & get_my_str() const {
      return _my_str;
    }
    */
    void set_my_str(const std::string & str) {
      _my_str = str;
    }
  private:
    optional<std::string> _my_str;
};

如果CCD_ 4(一个空字符串)可用作一个标记值;空/无效";在您的情况下声明,然后您可以这样做:

#include <string>
class my_class {
  public:
    my_class() /* default-initializes _my_str as "" */ { }
    my_class(const std::string & str) : _my_str(str) { }
    const std::string * get_my_str() const {
      if (!_my_str.empty()) // if it'a non-empty
        return &_my_str; // return the non-empty string
      else
        return nullptr; // if it's empty, return null
    }
    void set_my_str(const std::string & str) {
      _my_str = str;
    }
  private:
    std::string _my_str;
};

通常,您所指的模式称为Null对象模式。

实现它的"最古老的方法"是使用变量的一个可能值,并将其保留为"无值"的含义。在字符串的情况下,通常以这种方式使用空字符串。显然,当需要所有值时,这并不总是可能的。

"老办法"总是使用指针-(const T* get_t() const)。通过这种方式,整个变量值范围可能是有意义的,并且仍然可以通过返回null指针来获得"无值"语义。这是更好的,但指针使用起来仍然不舒服,也不安全。如今,指针通常是糟糕的工程。

现代的方式是optional<T>(或boost::optional<T>)。

  1. 空的std::string值并非每个定义都无效。它只是空的。

  2. 一个重要的区别是,第二种"get_…"方法不复制对象,而是向用户提供一个指向内部字符串的非常量指针,这会导致违反常量正确性,因为你暗示在get方法中使用const可能不会更改类,同时仍然提供一个可能更改内部状态的指针。

  3. 如果你的逻辑意味着"空字符串"=="无效",如果这是一种可能的状态,那么用户是否必须进行没有太大区别

    • if (get_my_str())) // use valid pointer to nonempty string
    • if(!get_my_str().empty()) // use valid nonempty string

我想。

  1. 您希望从get方法返回std::string const &,并将其留给用户来决定是否复制对象。

    4.1.无强制复制(相对于按值返回std::string

    4.2.没有可能为nullptr并意外被取消引用的指针。

    4.3.传递和存储可能比对象更长寿的指针比悬挂引用更常见。


我希望用户稍后能够初始化字符串,而不一定是在构造时,但我希望他们能够知道对象在字符串初始化之前是无效的。。。

问题是:在正确初始化之后,空字符串实际上是一个"有效"值吗?

  • 如果是:使用optional添加一个额外的状态信令有效性
  • 如果不是:让字符串的空表示对象无效