[英]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(毫無疑問,例外)。
這對我來說對於pair
的vector
,雖然它非常難看但我永遠不會在實際代碼中使用它:
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.