具有分配语义的非拥有持有者 [英] Non-owning holder with assignment semantics

查看:124
本文介绍了具有分配语义的非拥有持有者的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我有一个类应该持有对一些数据的引用,而不拥有该数据(即实际数据被保证不超出范围)。 现在,通常的实现(假设)是对数据的引用:

  struct holder_ref {
type const&值;

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

(请注意 const



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



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

  // x = other_x;被替换为:
x。〜T();
new(& x)T(other_x);

现在,这是符合标准的。但它肯定是丑陋的。没有 - 不可接受。



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

  struct holder_ptr {
type const * value;

//这是合法的吗?
holder_ptr(type const& value = 0):value(& value){}
};

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

  struct holder_ref {
type const&值;

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

holder_ref& operator =(holder_ref const& other){
//现在什么?
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);

//变体1.指针。
hptr [0] = holder_ptr(TEST1);

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

//变式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,我需要一个符合标准的解决方案。)

解决方案

我没有看到使用 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;
};

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



使用此解决方案,接口完全按照引用实现,但在引擎下,使用指针,以便类型是可分配的。在接口中使用引用确保没有使用指针所带来的问题(即,你永远不必担心指针是否为空)。



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


I have a class that should hold a reference to some data, without owning that data (i.e. the actual data is guaranteed not to go out of scope). In particular, the class cannot make a copy – the data is easily several gigabytes in size.

Now, the usual implementation (I assume) is to have a reference to the data:

struct holder_ref {
    type const& value;

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

(Please note that the constness has absolutely no bearing on the problem).

Now, I absolutely need this class to be assignable (i.e. have a working operator =). I thought this was a fairly common problem but I can’t remember how (if ever) I’ve solved it before.

The problem is that a reference cannot be assigned and there’s simply no way around this. The only solution I’ve come up with uses placement new in place of the assignment operator:

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

Now, this works and is standard compliant. But it sure is ugly. No – inacceptable.

So I’m searching for alternatives. One idea is to use pointers, but I’m unsure whether my constructor is actually guaranteed to work (and passing a pointer is impossible due to the interface I have to adhere to):

struct holder_ptr {
    type const* value;

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

But I’d rather use a reference, if at all possible. Only – how to implement the assignment operator?

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;
    }
};

As a test case, consider the following code:

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!
}

(Also, just to make this clear – the type we’re talking about is non-POD and I need a standard compliant solution.)

解决方案

I don't see anything wrong with using a holder_ptr. It can be implemented something like so:

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;
};

So long as you always assign to the pointer from a reference, you know that you have a valid object (that, or you ended up with a "null reference" previously, in which case you have other, bigger problems since you'll already have invoked undefined behavior).

With this solution, the interface is implemented entirely in terms of references, but under the hood a pointer is used so that the type is assignable. The use of references in the interface ensures there are none of the concerns that come with using pointers (namely, you never have to worry whether the pointer is null).

Edit: I've updated the example to allow for the holder to be default constructible.

这篇关于具有分配语义的非拥有持有者的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆