簡體   English   中英

帶有指向內部數據成員指針的容器的C ++類:復制/賦值

[英]C++ class with container of pointers to internal data members: copying/assignment

假設我有一個帶有容器數據成員d_members的類Widget ,以及另一個容器數據成員d_special_members其中包含指向d_special_members的可分辨元素的d_members 特殊成員在構造函數中確定:

#include <vector>

struct Widget
{
    std::vector<int> d_members;
    std::vector<int*> d_special_members;

    Widget(std::vector<int> members) : d_members(members)
    {
        for (auto& member : d_members)
            if (member % 2 == 0)
                d_special_members.push_back(&member);
    }
};

為這樣的類實現復制構造函數和operator=()的最佳方法是什么?

  • d_special_members在副本中應指向的副本d_members

  • 是否有必要重復構造函數中完成的工作? 我希望這可以避免。

  • 我可能想使用復制和交換習語

  • 我想可以使用索引而不是指針,但在我的實際用例中, d_members有類似std::vector< std::pair<int, int> > (而d_special_members仍然只是std::vector<int*> ,所以它指的是對的元素 ),所以這不會很方便。

  • 只有d_members的現有內容(在構造時給出) d_members類修改; 永遠不會有任何重新分配(這將使指針無效)。

  • 應該可以在運行時使用任意大小的d_members構造Widget對象。


請注意,默認分配/復制只復制指針:

#include <iostream>
using namespace std;

int main()
{
    Widget w1({ 1, 2, 3, 4, 5 });
    cout << "First special member of w1: " << *w1.d_special_members[0] << "\n";
    Widget w2 = w1;
    *w2.d_special_members[0] = 3;
    cout << "First special member of w1: " << *w1.d_special_members[0] << "\n";
}

產量

First special member of w1: 2
First special member of w1: 3

當數據移動到新的內存位置時,您要求的是一種維護關聯的簡單方法。 正如您所發現的,指針遠非理想。 你應該尋找的是相對的東西,比如指向成員的指針。 這在這種情況下並不十分適用,所以我會選擇最接近的替代方案:將索引存儲到子結構中。 因此,將一個索引存儲到向量中,並將一個標志指示該對的第一個或第二個元素(依此類推,如果您的結構變得更復雜)。

我看到的另一個選擇是遍歷舊對象中的數據,以確定給定特殊指針指向哪個元素 - 實質上是動態計算索引 - 然后在新對象中找到相應的元素並獲取其地址。 (也許您可以使用計算來加快速度,但我不確定它是否可移植。)如果有大量查找而沒有太多復制,這對整體性能可能更好。 但是,我寧願維護存儲索引的代碼。

最好的方法是使用指數。 老實說。 它使移動和復制正常工作; 這是一個非常有用的屬性,因為當您添加成員時,使用手寫副本很容易獲得靜默的錯誤行為。 將索引轉換為引用/指針的私有成員函數似乎並不十分繁重。

也就是說,可能仍有類似的情況,指數不是一個很好的選擇。 例如,如果你有一個unordered_map而不是一個vector ,你當然可以存儲鍵而不是指向值的指針,但是你會經歷一個昂貴的哈希。

如果你真的堅持使用指針而不是指數,我可能會這樣做:

struct Widget
{
    std::vector<int> d_members;
    std::vector<int*> d_special_members;

    Widget(std::vector<int> members) : d_members(members)
    {
        for (auto& member : d_members)
            if (member % 2 == 0)
                d_special_members.push_back(&member);
    }

    Widget(const Widget& other)
      : d_members(other.d_members)
      , d_special_members(new_special(other))
    {}
    Widget& operator=(const Widget& other) {
        d_members = other.d_members;
        d_special_members = new_special(other);
    }

private:
    vector<int*> new_special(const Widget& other) {
        std::vector<int*> v;
        v.reserve(other.d_special_members.size());
        std::size_t special_index = 0;
        for (std::size_t i = 0; i != d_members.size(); ++i) {
            if (&other.d_members[i] == other.d_special_members[special_index]) {
              v.push_back(&d_members[i});
              ++special_index;
            }
        }
        return v;
    }
};

我的實現以線性時間運行並且不使用額外空間,但利用了事實(基於您的示例代碼),指針中沒有重復,並且指針的排序與原始數據相同。

我避免復制和交換,因為沒有必要避免代碼重復,並且沒有任何理由。 實現強大的異常安全是一個可能的性能影響,就是這樣。 但是,編寫一個通用的CAS,可以為任何正確實現的類提供強大的異常安全性,這是微不足道的。 類編寫者通常不應該為賦值運算符使用copy和swap(毫無疑問,例外)。

這對我來說對於pairvector ,雖然它非常難看但我永遠不會在實際代碼中使用它:

std::vector<std::pair<int, int>> d_members;
std::vector<int*> d_special_members;

Widget(const Widget& other) : d_members(other.d_members) {
   d_special_members.reserve(other.d_special_members.size());
   for (const auto p : other.d_special_members) {
      ptrdiff_t diff = (char*)p - (char*)(&other.d_members[0]);
      d_special_members.push_back((int*)((char*)(&d_members[0]) + diff));
   }
}

為簡潔起見,我只使用類似C的類型轉換, reinterpret_cast會更好。 我不確定這個解決方案是否會導致未定義的行為 ,事實上我猜它確實如此,但我敢說大多數編譯器都會生成一個工作程序。

我認為使用索引而不是指針是完美的。 那么您不需要任何自定義復制代碼。 為方便起見,您可能希望定義一個將索引轉換為所需實際指針的成員函數。 那么你的成員可以是任意復雜的。

private:
    int* getSpecialMemberPointerFromIndex(int specialIndex)
    {
        return &d_member[specialIndex];
    }

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

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