0

As I know,
the purpose of void constructor is to reset all the element from choas state to proper new-born state.
and this statment is quite accruate for me to do it after steal data from rvalue referance.
but it seems to be a invild use for direct call to void constructor,and placement-new not work for this.

so,

  • should I call void constructor to reset object in move semantic?
  • should I write reset() and invoke() like this one?
  • what should void constructor do?
  • is my string inplmentation good enough?
#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;
};
AiDSl
  • 25
  • 6

2 Answers2

2

I'm only going to address the question about move semantics here, because you should only ask one question at a time.

The move constructor can generally leave the object in whatever state it wants, as long as you document the postconditions so that users of the class know what to expect.

For example, many classes in the C++ standard library declare that a moved-from object is in an unspecified but valid state. This means that you can't make any assumptions about the state of the object, but you can ask the object what its state is or you can manually reset it to a clear state. (The "proper" way to say this is that you can only call methods of the object that have no preconditions.)

I believe this route was chosen because the most efficient way to implement move semantics is simply to swap the values of the target and source objects. Usually this is fine because in many scenarios, the source object is either going to be destroyed right away or manually cleared.

The way you'd typically do this is to:

  1. Implement a valid swap() method:

    void STR_imp::swap(STR_imp & other) {
      std::swap(cstr, other.cstr);
    }
    
  2. On move-assignment, just swap:

    STR_imp& STR_imp::operator=(STR_imp&& instr) {
      swap(instr);
      return *this;
    }
    
  3. On move-construction, construct the default state and then swap:

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

This pattern has multiple advantages. The biggest advantage is that it's really hard to get wrong if your swap() method works correctly. Another advantage is that it forces you to support swapping, which is useful on its own in many different scenarios.

cdhowie
  • 133,716
  • 21
  • 261
  • 264
  • so,self answering here(might be wrong): 1,3.the constructor I was talk about should only apply to object in invalid state which only in case of non-constructed object,and in that object life time,it should always valid but could be unspecified . – AiDSl Apr 23 '20 at 18:14
0

A destructor in C++ destroys your object as its name states meaning that after calling it the underlying memory of your object has been freed. This also means that you cannot access it anymore since its not present in your application.

If you want to reset an object, you can just call your reset function, which will clear the data the way you want. If you want a clean copy of your object, you can use the move assignment operator and then call reset on the copied object.

Note that setting cstr to nullptr doesn't free the memory. Here is what you should do instead:

~STR_imp() { delete cstr; }

STR_imp& reset()
{
  delete cstr;
  cstr = nullptr;
  return *this;
}
Thomas Caissard
  • 761
  • 3
  • 9