C++primer5thedition.Unionandmembersofclasstype

I have this text from C++ primer 5th edition. ch 19.6 Union:

class Token {
public:
    Token(): tok(INT), ival{0} { }
    Token(const Token &t): tok(t.tok) { copyUnion(t); }
    Token &operator=(const Token&);
    ~Token() { if (tok == STR) sval.~string(); }
    Token &operator=(const std::string&);
    Token &operator=(char);
    Token &operator=(int);
    Token &operator=(double);
private:
    enum {INT, CHAR, DBL, STR} tok; // discriminant
    union {                         // anonymous union
        char   cval;
        int    ival;
        double dval;
        std::string sval;
    }; // each Token object has an unnamed member of this unnamed union type
    // check the discriminant and copy the union member as appropriate
    void copyUnion(const Token&);
};

Token &Token::operator=(const std::string &s)
{
    if (tok == STR) // if we already hold a string, just do an assignment
       sval = s;
    else
       new(&sval) string(s); // otherwise construct a string
    tok = STR;                // update the discriminant
    return *this;
}

In this case, if the union already holds a string, we can use the normal string assignment operator to give a new value to that string. Otherwise, there is no existing string object on which to invoke the string assignment operator. Instead, we must construct a string in the memory that holds the union. We do so using placement new (§ 19.1.2, p. 824) to construct a string at the location in which sval resides. We initialize that string as a copy of our string parameter. We next update the discriminant and return.

Everything is so straightforward for me but what confuses me is this version of the copy-assignment operator (that takes an std::string): and the last paragraph: "if the union already holds a string..." But in case the union doesn't hold a string then why we bother to use such placement new? as long as the assignment operator calls the destructor thus destroys the object then frees memory which is the reverse of the constructor?

I mean why he didn't directly write this?

sval = s;

I think there's no need to call the dtor explicitly with sval.~string() because the assignment operator does that instead. I mean no condition is required.

回答

You cannot assign something to an object that was never constructed. In C++, any object, any object all, needs to be constructed first, before anything happens to it. This is one of the fundamental rules of C++, without any exceptions or alternatives.

Attempting to use an object in any way, before it is constructed, results in undefined behavior. Assigning to an object counts as using it. Because, after all, if you're assigning something to an object it must exist already.

This is a key, fundamental concept. There is a big difference between constructing an object and assigning something to it. In the first case the object did not exist originally. In the second case it already exists, which means that at some unspecified prior point in time it must've been constructed.

You are proposing, effectively, to assign something to an object without the benefit of constructing it. This is undefined behavior.

P.S. In modern C++, you can pretty much ignore everything you've read, and simply use std::variant, which takes care of all of these details for you. It is true, however, that it is important to understand these kinds of fundamental concepts, and this is a good example that illustrates them. But after you've figured out why things have to be the way they are, here, you can pretty much forget about it, and just use std::variant.

I think there's no need to call the dtor explicitly with
sval.~string() because the assignment operator does that instead.

不,作为一般规则:赋值运算符不会调用析构函数。赋值运算符通常不会销毁对象。如果是这样,该对象将不再存在,但它当然会在分配给它之后存在。


以上是C++primer5thedition.Unionandmembersofclasstype的全部内容。
THE END
分享
二维码
< <上一篇
下一篇>>