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

C++ 如何复制(或交换)包含引用或常量成员的类型的对象?,c++,swap,copy-constructor,C++,Swap,Copy Constructor,我试图解决的问题是,创建包含引用和常量数据成员的对象的容器,例如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:

我试图解决的问题是,创建包含引用和常量数据成员的对象的容器,例如
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;
};
这明确地消除了
number
const
属性,并消除了
foo\u引用的隐含
const
属性。这不是我想要的解决方案。如果这是唯一的非UB解决方案,那就这样吧。我也非常了解这种解决方案:

struct Bar {
  Bar (Foo & foo, int num) : foo_ptr(&foo), number(num) {}
private:
  Foo * foo_ptr;
  int number;
  // Mutable member data elided
};
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));
}
然后使用复制和交换写入赋值运算符。这绕过了引用和常量问题,但它是UB吗?(至少它没有使用
reinterpret\u cast
const\u cast
)一些被省略的可变数据是包含
std::vector
的对象,所以我不知道这样的浅拷贝在这里是否有效

然而,这失去了引用相对于指针的优势

没有优势。指针和引用是不同的,但没有一个是更好的。如果传递nullptr是有效的,则使用引用来确保存在有效实例和指针。在您的示例中,可以传递引用并存储指针

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

无法重新放置引用。只需将成员存储为指针,就像在所有其他具有可分配类的库中一样

如果您想保护自己免受攻击,请将int和指针移动到基类的private部分。添加受保护的函数,以仅公开int成员进行读取和对指针成员的引用(例如,防止您将该成员视为数组)


(顺便说一句,它不必是基类。也可以是具有公共访问器的成员。)

如果使用移动操作符实现此功能,有一种方法:

Bar & Bar :: operator = (Bar && source) {
    this -> ~ Bar ();
    new (this) Bar (std :: move (source));
    return *this;
}
您不应该对复制构造函数使用这种技巧,因为它们经常会抛出,这是不安全的。移动构造函数永远不应该抛出,所以这应该是可以的

std::vector
和其他容器现在尽可能利用移动操作,所以调整大小和排序等都可以

这种方法允许您保留常量和引用成员,但仍然无法复制对象。为此,必须使用非常量和指针成员

顺便说一下,对于非POD类型,您不应该像这样使用memcpy

编辑 对未定义行为投诉的回应

问题似乎是

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
如果继续使用
foo
,则为未定义行为。对我来说,这和

X * x = new X ();
const int & foo = x.member;
delete x;
很明显,使用
foo
是无效的

也许对
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
成员指针
ptr
在移动
x
后仍然有效,但
foo
不会

那个康斯特成员真的应该是康斯特

那你就不能重新分配对象了,是吗?因为这会改变你刚才说的不应该改变的东西的值:在赋值
foo.x
为1,
bar.x
为2之前,如果你做了
foo=bar
,那么如果
foo.x
“真的应该是常量”,那么会发生什么呢?您已经告诉它要修改
foo.x
,这确实不应该被修改

向量的元素就像
foo
,它是容器有时会修改的对象

Pimpl可能是这里的出路。动态分配包含所有数据成员(包括常量成员和引用)的对象(“impl”)。在向量中的对象中存储指向该对象(“p”)的指针。然后,
swap
很简单(交换指针),移动分配也是如此,复制分配可以通过构造一个新的impl并删除旧的impl来实现


然后,Impl上的任何操作都会保留数据成员的常量和不可重置性,但少量与生命周期相关的操作可以直接作用于p。

您可以组成您的成员类,这些成员可以考虑这些限制,但可以自行分配

#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));
};
#包括
模板
类只读包装器
{
T值;
公众:
显式只读包装器(常量T&T):值(T){}
常量T&get()常量{返回值;}
运算符常量T&()常量{返回值;}
};
结构Foo{};
结构条{
条(Foo&Foo,int-num):Foo_引用(Foo),数字(num){}
私人:
std::reference\u wrapper foo\u reference;//C++11,Boost也有一个
只读包装编号;
//删除可变成员数据
};
#包括
int main()
{
std::向量条\向量;
富富,;
bar_向量。推回(bar(foo,10));
};

这是我已经在做的事情,如果可能的话,我希望避免这样做。在网络上有很多交换的实现,它们进行交换引用,但是它们调用UB。它在他们的计算机上,用他们的编译器工作,在我的计算机上也工作。但它仍然是UB。参考文献是不同的。它们可能会产生相同的最终机器代码,但语义是不同的。作为数据成员的引用在行为上非常接近于已知为非null的
const
指针。将引用更改为
const
指针并不能解决问题;它仍然是
const
。表达式“X比Y的优势”通常指为特定目的对用户有利的任何差异。说X比Y有优势并不意味着X在各个方面都严格优于Y。事实上,同样的区别可能是一个目的是“X优于Y”,但另一个目的是“Y优于X”。这是我已经实现的解决方案。最好有一个解决方案,它不涉及更改指针的引用或删除
const
qualifi
#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));
};