簡體   English   中英

如何復制(或交換)包含引用或const成員的類型的對象?

[英]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_castconst_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
};

這明確地消除了numberconst ,並消除了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_castconst_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

成員指針ptrx的移動中幸存,但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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM