我应该调用 void 构造函数来重置移动语义中的对象吗?

should I call void constructor to reset object in move semantic?

据我所知,
void 构造函数的目的是将所有元素从 choas 状态重置为正确的新生状态。
在从右值参考中窃取数据后,这个声明对我来说是相当准确的。
但它似乎是直接调用 void 构造函数的 invild 用法,而 placement-new 不适用于此。

所以,

#include <cstring>


class STR_imp
{
    char* cstr;

    static size_t getSize(const char* incstr)
    {
        return std::strlen(incstr)+1;
    };
    STR_imp& fillBy(const char* incstr)
    {
        for(int i=0,size=getSize(incstr);i<size;i++)
            this->cstr[i]=incstr[i];
        return *this;
    };
    STR_imp& reset()
    {
        this->cstr=nullptr;
        return *this;
    };
    STR_imp& invoke()
    {
        if(this->cstr!=nullptr)
            delete[] this->cstr;
        return *this;
    };

    public:
        STR_imp():cstr(nullptr)
        {};
        ~STR_imp()
        {this->invoke().reset();};

        STR_imp(const char* const& incstr);//splited for reading
        STR_imp(char*&& incstr):cstr(incstr)
        {
            incstr=nullptr;
        };
        STR_imp(const STR_imp& instr);//splited for reading
        STR_imp(STR_imp&& instr):cstr(instr.cstr)
        {
            instr.reset();
        };
        STR_imp& operator= (const char* const& incstr);//splited for reading
        STR_imp& operator= (char*&& incstr)
        {
            this->invoke();
            this->cstr=incstr;
            incstr=nullptr;
            return *this;
        };
        STR_imp& operator= (const STR_imp& instr);//splited for reading
        STR_imp& operator= (STR_imp&& instr)
        {
            this->invoke();
            this->cstr=instr.cstr;
            instr.reset();
            return *this;
        };
        char* operator() ()
        {
            return this->cstr;
        };
};
STR_imp::STR_imp(const char* const& incstr):cstr(new char[getSize(incstr)])
{
    this->fillBy(incstr);
};
STR_imp::STR_imp(const STR_imp& instr):cstr(new char[getSize(instr.cstr)])
{
    this->fillBy(instr.cstr);
};
STR_imp& STR_imp::operator= (const char* const& incstr)
{
    this->invoke();
    this->cstr=new char[getSize(incstr)];
    this->fillBy(incstr);
    return *this;
};
STR_imp& STR_imp::operator= (const STR_imp& instr)
{
    this->invoke();
    this->cstr=new char[getSize(instr.cstr)];
    this->fillBy(instr.cstr);
    return *this;
};

C++ 中的析构函数会销毁您的对象,正如其名称所述,这意味着在调用它之后,您的对象的底层内存已被释放。这也意味着您无法再访问它,因为它不存在于您的应用程序中。

如果你想重置一个对象,你可以调用你的 reset 函数,它会按照你想要的方式清除数据。如果您想要对象的干净副本,可以使用移动赋值运算符,然后对复制的对象调用 reset

请注意,将 cstr 设置为 nullptr 不会释放内存。这是你应该做的:

~STR_imp() { delete cstr; }

STR_imp& reset()
{
  delete cstr;
  cstr = nullptr;
  return *this;
}

我只打算在这里解决有关移动语义的问题,因为您一次只能问一个问题。

移动构造函数通常可以将对象保留在它想要的任何状态,只要您记录后置条件,以便 class 的用户知道会发生什么。

例如,C++ 标准库中的许多 class 声明移出的对象处于 未指定但有效的 状态。这意味着您不能对对象的状态做出任何假设,但您可以询问对象它的状态是什么,或者您可以手动将其重置为清晰状态。 ("proper" 的说法是,您只能调用没有先决条件的对象的方法。)

我相信之所以选择这条路线,是因为实现移动语义的最有效方法就是简单地交换目标对象和源对象的值。通常这很好,因为在许多情况下,源对象要么立即销毁,要么手动清除。

您通常这样做的方式是:

  1. 实施有效的swap()方法:

    void STR_imp::swap(STR_imp & other) {
      std::swap(cstr, other.cstr);
    }
    
  2. 在移动分配时,只需交换:

    STR_imp& STR_imp::operator=(STR_imp&& instr) {
      swap(instr);
      return *this;
    }
    
  3. 移动构造时,构造默认状态然后交换:

    STR_imp::STR_imp(STR_imp&& instr) : STR_imp{} {
      swap(instr);
    }
    

这种模式有很多优点。最大的优点是,如果您的 swap() 方法正确工作,真的 很难出错。另一个优点是它强制您支持交换,这在许多不同的场景中都是有用的。