[英]How to copy (or swap) objects of a type that contains members that are references or const?
我試圖解決的問題出現在制作容器,例如包含引用和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;
};
這不會按原樣工作,因為由於引用成員foo_reference
和const成員number
無法構建類Foo
的默認賦值運算符。
一種解決方案是將foo_reference
更改為指針並刪除const
關鍵字。 然而,這會失去引用優於指針的優點,並且const
成員確實應該是const
。 他們是私人成員,所以唯一可以造成傷害的是我自己的代碼,但是我用自己的代碼射擊了自己的腳(或更高)。
我已經在swap
方法的形式上看到了Web上的這個問題的解決方案,這些方法似乎充滿了基於reinterpret_cast
和const_cast
的奇跡的未定義行為。 碰巧這些技術確實在我的計算機上運行。 今天。 使用一個特定編譯器的特定版本。 明天還是用不同的編譯器? 誰知道。 我不打算使用依賴於未定義行為的解決方案。
stackoverflow的相關問題:
那么有沒有辦法為這樣一個不調用未定義行為的類編寫swap
方法/復制構造函數,或者我只是搞砸了?
編輯
為了說清楚,我已經非常了解這個解決方案:
struct Bar {
Bar (Foo & foo, int num) : foo_ptr(&foo), number(num) {}
private:
Foo * foo_ptr;
int number;
// Mutable member data elided
};
這明確地消除了number
的const
,並消除了foo_reference
的隱含const
。 這不是我追求的解決方案。 如果這是唯一的非UB解決方案,那就這樣吧。 我也很清楚這個解決方案:
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));
}
然后使用copy-and-swap編寫賦值運算符。 這解決了引用和const問題,但它是UB嗎? (至少它不使用reinterpret_cast
和const_cast
。)一些省略的可變數據是包含std::vector
的對象,所以我不知道這樣的淺拷貝是否可以在這里工作。
您無法重置參考。 只需將成員存儲為指針,就像在具有可分配類的所有其他庫中一樣。
如果您想保護自己,請將int和指針移動到基類的私有部分。 添加受保護的函數以僅公開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
};
(順便說一句,它不一定是基類。也可以是公共訪問者的成員。)
如果使用移動運算符實現此方法,則有一種方法:
Bar & Bar :: operator = (Bar && source) {
this -> ~ Bar ();
new (this) Bar (std :: move (source));
return *this;
}
你不應該在復制構造函數中使用這個技巧,因為它們經常拋出然后這是不安全的。 移動構造函數永遠也不會扔了,所以這應該是確定。
std::vector
和其他容器現在盡可能地利用移動操作,因此調整大小和排序等等都可以。
此方法將允許您保留const和引用成員,但您仍然無法復制該對象。 要做到這一點,你必須使用非const和指針成員。
順便說一下,對於非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
沒有。
const成員真的應該是const
那么,你不能重新分配對象,可以嗎? 因為這會改變你剛剛說過的東西真的不應該改變的價值:在foo.x
之前foo.x
是1而bar.x
是2,你做foo = bar
,那么如果foo.x
“真的應該是const“那么應該發生什么? 你告訴它修改foo.x
,真的不應該修改。
向量的元素就像foo
一樣,它是容器有時會修改的對象。
Pimpl可能是去這里的方式。 動態分配包含所有數據成員的對象(“impl”),包括const和引用。 在對象中存儲指向該對象的指針(“p”)。 然后swap
是微不足道的(交換指針),移動分配也是如此,並且可以通過構造新的impl並刪除舊的impl來實現復制分配。
然后,Impl上的任何操作都會保留數據成員的常量和不可重用性,但是少量與生命周期相關的操作可以直接作用於P.
然而,這會失去引用優於指針的優點
沒有優勢。 指針和參考不同,但沒有一個是更好的。 如果傳遞nullptr有效,則使用引用確保存在有效實例和指針。 在您的示例中,您可以傳遞引用並存儲指針
struct Bar {
Bar (Foo & foo) : foo_reference(&foo) {}
private:
Foo * foo_reference;
};
您可以組成您的成員類來處理這些限制但可以自行分配。
#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.