具有赋值语义的非所有持有者

Posted

技术标签:

【中文标题】具有赋值语义的非所有持有者【英文标题】:Non-owning holder with assignment semantics 【发布时间】:2011-03-18 02:48:00 【问题描述】:

我有一个类应该包含对某些数据的引用,而不拥有该数据(即保证实际数据不会超出范围)。 特别是,该类不能进行复制——数据大小很容易达到几 GB。

现在,通常的实现(我假设)是引用数据:

struct holder_ref 
    type const& value;

    holder_ref(type const& value) : value(value)  
;

(请注意constness 与问题完全无关)。

现在,我绝对需要这个类是可分配的(即有一个工作的operator =)。我认为这是一个相当普遍的问题,但我不记得我以前是如何解决的(如果有的话)。

问题是无法分配引用,而且根本没有办法解决这个问题。我想出的唯一解决方案是使用placement new 代替赋值运算符:

// x = other_x; gets replaced with:
x.~T();
new (&x) T(other_x);

现在,这可以正常工作并且符合标准。但它肯定是丑陋的。不——不可接受。

所以我正在寻找替代品。一个想法是使用指针,但我不确定我的构造函数是否真的能保证工作(由于我必须遵守接口,传递指针是不可能的):

struct holder_ptr 
    type const* value;

    // Is this legal?
    holder_ptr(type const& value = 0) : value(&value)  
;

但如果可能的话,我宁愿使用参考。只有——赋值运算符如何实现?

struct holder_ref 
    type const& value;

    holder_ref(type const& value = 0) : value(value)  

    holder_ref& operator =(holder_ref const& other) 
        // Now what?!
        return *this;
    
;

作为一个测试用例,考虑下面的代码:

int main() 
    int const TEST1 = 23;
    int const TEST2 = 13;
    int const TEST3 = 42;
    std::vector<holder_ptr> hptr(1);
    std::vector<holder_ref> href(2);

    // Variant 1. Pointer.
    hptr[0] = holder_ptr(TEST1);

    // Variant 2. Placement new.
    href[0].~holder_ref();
    new (&href[0]) holder_ref(TEST2);

    // Variant 3. ???
    href[1] = holder_ref(TEST3);

    assert(*hptr[0].value == TEST1);   // Works (?)
    assert(href[0].value == TEST2);    // Works
    assert(href[1].value == TEST3);    // BOOM!

(另外,澄清一下——我们所说的类型是非 POD,我需要一个符合标准的解决方案。)

【问题讨论】:

指针解决方案有什么问题?它似乎完全符合您的用例。它是一个可分配的成员,并不表示或暗示所有权。 虽然你不能合法地存储一个指向临时的指针,所以你的默认值 value:holder_ptr(type const&amp; value = 0) 是不明智的。 (但这也适用于参考。) @Charles:是的,这是参考解决方案的另一个问题。 @Konrad:如果持有者需要默认构造,那么你真的不能存储引用——如果你默认构造它,你将没有用于初始化引用的对象。也许我错过了什么? @Charles:你需要多回答,少评论。总而言之,您的 cmets 对这个问题最有帮助(尽管其他答案也很好)。可惜不能奖励。 【参考方案1】:

我认为使用holder_ptr 没有任何问题。它可以像这样实现:

struct bad_holder : std::exception  ;

struct holder_ptr 
    holder_ptr() : value(0)  
    holder_ptr(type const& value) : value(&value)  

    type const& get()  
        if (value == 0) throw bad_holder();
        return *value; 
    
private:
    type const* value;
;

只要你总是从引用中分配给指针,你就知道你有一个有效的对象(那个,或者你之前得到了一个“空引用”,在这种情况下你还有其他更大的问题,因为你'将已经调用了未定义的行为)。

使用此解决方案,接口完全根据引用来实现,但在底层使用了一个指针,以便类型是可分配的。在接口中使用引用可确保没有使用指针所带来的问题(即,您永远不必担心指针是否为空)。

编辑:我已经更新了示例以允许持有人默认可构造。

【讨论】:

您也可以将其实现为一种指针包装器并重载*-&gt; 而不是使用get()...【参考方案2】:

我会使用指针支架。但是,如果您对此坚决反对,那么如何隐藏您的展示位置 new operator=

holder_ref& operator =(holder_ref const& other) 
    new (this) holder_ref(other);
    return *this;

【讨论】:

哦,我忘记了那个解决方案。漂亮(按 C++ 标准)! (只是,它需要事先调用析构函数。) 它还需要防止自我分配。和 Herb Sutter 一样,我真的不喜欢这种技术。如果有人从 holder_ref 派生(可能不太可能),那就太糟糕了。另外,请参见此处:gotw.ca/gotw/023.htm @Charles:公平地说,GotW #23 中提出的观点不适用于此处(实际上我可以保证,由于图书馆的限制),除了第 6 点和第 7 点,我认为它们是基于标准错误的迂腐(即技术上忠实于字母,但可能是无意的并且普遍理解不同),并且在实践中并不相关,对于第 4 点,这是一个真可惜。 更正:第 4 点与我无关。所以在我的情况下,这个解决方案在技术上是合理的。【参考方案3】:

是否足够符合 TR1 weak_ptr 标准?

【讨论】:

是的 - 不幸的是,TR1 和 Boost 一样不参与该项目(我知道,我知道,愚蠢的......)。我什至没有想到这一点。 weak_ptr 仅在某人拥有shared_ptr 的数据时才有效。即使是这种情况,鉴于“保证实际数据不会超出范围”,这也有点毫无意义。

以上是关于具有赋值语义的非所有持有者的主要内容,如果未能解决你的问题,请参考以下文章

具有共享所有者语义的容器

为啥 ~= 在 C++ 中缺少唯一的非逻辑赋值运算符? [关闭]

再进一步: 根据语义 去决定采用的形式:“持有自己独立的副本” vs "作为'引用者'之一 " ?

在 verilog中的非阻塞赋值在啥时候赋值时刻结束?

多重赋值语义

对象赋值的语义