简体   繁体   English

如何复制(或交换)包含引用或const成员的类型的对象?

[英]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: 我试图解决的问题出现在制作容器,例如包含引用和const数据成员的std::vector对象:

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 . 这不会按原样工作,因为由于引用成员foo_reference和const成员number无法构建类Foo的默认赋值运算符。

One solution is to change that foo_reference to a pointer and get rid of the const keyword. 一种解决方案是将foo_reference更改为指针并删除const关键字。 This however loses the advantages of references over pointers, and that const member really should be const . 然而,这会失去引用优于指针的优点,并且const成员确实应该是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 . 我已经在swap方法的形式上看到了Web上的这个问题的解决方案,这些方法似乎充满了基于reinterpret_castconst_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: 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? 那么有没有办法为这样一个不调用未定义行为的类编写swap方法/复制构造函数,或者我只是搞砸了?

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 const ness of number and the eliminates the implied const ness of foo_reference . 这明确地消除了numberconst ,并消除了foo_reference的隐含const This is not the solution I am after. 这不是我追求的解决方案。 If this is the only non-UB solution, so be it. 如果这是唯一的非UB解决方案,那就这样吧。 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. 然后使用copy-and-swap编写赋值运算符。 This gets around the reference and const problems, but is it UB? 这解决了引用和const问题,但它是UB吗? (At least it doesn't use reinterpret_cast and const_cast .) Some of the elided mutable data are objects that contain std::vector s, so I don't know if a shallow copy like this will work here. (至少它不使用reinterpret_castconst_cast 。)一些省略的可变数据是包含std::vector的对象,所以我不知道这样的浅拷贝是否可以在这里工作。

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. 如果您想保护自己,请将int和指针移动到基类的私有部分。 Add protected functions to only expose the int member for reading and a reference to the pointer member (eg to prevent yourself from treating the member as an array). 添加受保护的函数以仅公开int成员以进行读取和对指针成员的引用(例如,以防止自己将成员视为数组)。

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.) (顺便说一句,它不一定是基类。也可以是公共访问者的成员。)

If you implement this with move operators there is a way: 如果使用移动运算符实现此方法,则有一种方法:

Bar & Bar :: operator = (Bar && source) {
    this -> ~ Bar ();
    new (this) Bar (std :: move (source));
    return *this;
}

You shouldn't really use this trick with copy constructors because they can often throw and then this isn't safe. 你不应该在复制构造函数中使用这个技巧,因为它们经常抛出然后这是不安全的。 Move constructors should never ever throw, so this should be OK. 移动构造函数永远也不会扔了,所以这应该是确定。

std::vector and other containers now exploit move operations wherever possible, so resize and sort and so on will be OK. std::vector和其他容器现在尽可能地利用移动操作,因此调整大小和排序等等都可以。

This approach will let you keep const and reference members but you still can't copy the object. 此方法将允许您保留const和引用成员,但您仍然无法复制该对象。 To do that, you would have to use non-const and pointer members. 要做到这一点,你必须使用非const和指针成员。

And by the way, you should never use memcpy like that for non-POD types. 顺便说一下,对于非POD类型,你永远不应该像这样使用memcpy。

Edit 编辑

A response to the Undefined Behaviour complaint. 对未定义行为投诉的回复。

The problem case seems to be 问题似乎是

struct X {
    const int & member;
    X & operator = (X &&) { ... as above ... }
    ...
};

X x;
const int & foo = x.member;
X = std :: move (some_other_X);
// foo is no longer valid

True it is undefined behaviour if you continue to use foo . 如果你继续使用foo那么它是未定义的行为。 To me this is the same as 对我来说,这是一样的

X * x = new X ();
const int & foo = x.member;
delete x;

in which it is quite clear that using foo is invalid. 很明显,使用foo是无效的。

Perhaps a naive read of the X::operator=(X&&) would lead you to think that perhaps foo is still valid after a move, a bit like this 或许天真地读取X::operator=(X&&)会让你觉得foo在移动后仍然有效,有点像这样

const int & (X::*ptr) = &X::member;
X x;
// x.*ptr is x.member
X = std :: move (some_other_X);
// x.*ptr is STILL x.member

The member pointer ptr survives the move of x but foo does not. 成员指针ptrx的移动中幸存,但foo没有。

that const member really should be const const成员真的应该是const

Well, then you can't reassign the object, can you? 那么,你不能重新分配对象,可以吗? Because that would change the value of something that you've just said really should not change: before the assigment foo.x is 1 and bar.x is 2, and you do foo = bar , then if foo.x "really should be const" then what's supposed to happen? 因为这会改变你刚刚说过的东西真的不应该改变的价值:在foo.x之前foo.x是1而bar.x是2,你做foo = bar ,那么如果foo.x “真的应该是const“那么应该发生什么? You've told it to modify foo.x , which really should not be modified. 你告诉它修改foo.x ,真的不应该修改。

An element of a vector is just like foo , it's an object that the container sometimes modifies. 向量的元素就像foo一样,它是容器有时会修改的对象。

Pimpl might be the way to go here. Pimpl可能是去这里的方式。 Dynamically allocate an object (the "impl") containing all your data members, including const ones and references. 动态分配包含所有数据成员的对象(“impl”),包括const和引用。 Store a pointer to that object (the "p") in your object that goes in the vector. 在对象中存储指向该对象的指针(“p”)。 Then swap is trivial (swap the pointers), as is move assignment, and copy assignment can be implemented by constructing a new impl and deleting the old one. 然后swap是微不足道的(交换指针),移动分配也是如此,并且可以通过构造新的impl并删除旧的impl来实现复制分配。

Then, any operations on the Impl preserve the const-ness and un-reseatable-ness of your data members, but a small number of lifecycle-related operations can act directly on the P. 然后,Impl上的任何操作都会保留数据成员的常量和不可重用性,但是少量与生命周期相关的操作可以直接作用于P.

This however loses the advantages of references over pointers 然而,这会失去引用优于指针的优点

There is no advantage. 没有优势。 Pointers and reference are different, but none is the better one. 指针和参考不同,但没有一个是更好的。 You use a reference to ensure that there is a valid instance and a pointer if passing a nullptr is valid. 如果传递nullptr有效,则使用引用确保存在有效实例和指针。 In your example you could pass a reference and store a pointer 在您的示例中,您可以传递引用并存储指针

struct Bar {
   Bar (Foo & foo) : foo_reference(&foo) {}
private:
   Foo * foo_reference;
};

You can compose your class of members that take care of those restrictions but are assignable themselves. 您可以组成您的成员类来处理这些限制但可以自行分配。

#include <functional>

template <class T>
class readonly_wrapper
{
    T value;
public:
    explicit readonly_wrapper(const T& t): value(t) {}
    const T& get() const { return value; }
    operator const T& () const { return value; }
};

struct Foo{};

struct Bar {
  Bar (Foo & foo, int num) : foo_reference(foo), number(num) {}
private:
  std::reference_wrapper<Foo> foo_reference;  //C++11, Boost has one too
  readonly_wrapper<int> number;
  // Mutable member data elided
};

#include <vector>
int main()
{
  std::vector<Bar> bar_vector;
  Foo foo;
  bar_vector.push_back(Bar(foo, 10));
};

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM