Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
246 views
in Technique[技术] by (71.8m points)

c++ - How to copy (or swap) objects of a type that contains members that are references or const?

The problem I am trying to address arises with making containers such as an std::vector of objects that contain reference and const data members:

struct Foo;

struct Bar {
  Bar (Foo & foo, int num) : foo_reference(foo), number(num) {}
private:
  Foo & foo_reference;
  const int number;
  // Mutable member data elided
};

struct Baz {
  std::vector<Bar> bar_vector;
};

This won't work as-is because the default assignment operator for class Foo can't be built due to the reference member foo_reference and const member number.

One solution is to change that foo_reference to a pointer and get rid of the const keyword. This however loses the advantages of references over pointers, and that const member really should be const. They are private members, so the only thing that can do harm is my own code, but I have shot myself in the foot (or higher) with my own code.

I've seen solutions to this problem on the web in the form of swap methods that appear to be chock full of undefined behavior based on the wonders of reinterpret_cast and const_cast. It happens that those techniques do appear to work on my computer. Today. With one particular version of one particular compiler. Tomorrow, or with a different compiler? Who knows. I am not going to use a solution that relies on undefined behavior.

Related answers on stackoverflow:

So is there a way to write a swap method / copy constructor for such a class that does not invoke undefined behavior, or am I just screwed?

Edit
Just to make it clear, I already am quite aware of this solution:

struct Bar {
  Bar (Foo & foo, int num) : foo_ptr(&foo), number(num) {}
private:
  Foo * foo_ptr;
  int number;
  // Mutable member data elided
};

This explicitly eliminates the constness of number and the eliminates the implied constness of foo_reference. This is not the solution I am after. If this is the only non-UB solution, so be it. I am also quite aware of this solution:

void swap (Bar & first, Bar & second) {
    char temp[sizeof(Bar)];
    std::memcpy (temp, &first, sizeof(Bar));
    std::memcpy (&first, &second, sizeof(Bar));
    std::memcpy (&second, temp, sizeof(Bar));
}

and then writing the assignment operator using copy-and-swap. This gets around the reference and const problems, but is it UB? (At least it doesn't use reinterpret_cast and const_cast.) Some of the elided mutable data are objects that contain std::vectors, so I don't know if a shallow copy like this will work here.

See Question&Answers more detail:os

与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

You can't reseat the reference. Just store the member as a pointer, as it is done in all other libraries with assignable classes.

If you want to protect yourself from yourself, move the int and the pointer to the private section of a base class. Add protected functions to only expose the int member for reading and a reference to the pointer member (e.g to prevent yourself from treating the member as an array).

class BarBase
{
    Foo* foo;
    int number;
protected:
    BarBase(Foo& f, int num): foo(&f), number(num) {}
    int get_number() const { return number; }
    Foo& get_foo() { return *foo; }
    const Foo& get_foo() const { return *foo; }
};

struct Bar : private BarBase {
  Bar (Foo & foo, int num) : BarBase(foo, num) {}

  // Mutable member data elided
};

(BTW, it doesn't have to be a base class. Could also be a member, with public accessors.)


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...