views:

74

answers:

3

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.)

+1  A: 

Is a TR1 weak_ptr standard compliant enough?

Peter G.
Yes – unfortunately, TR1 is out for the project, as is Boost (I know, I know, stupid …). I didn’t even think of this.
Konrad Rudolph
`weak_ptr` is only valid if someone is owning the data with a `shared_ptr`. Even if this is the case it's a bit pointless given that "the actual data is guaranteed not to go out of scope".
Charles Bailey
+5  A: 

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.

James McNellis
You could also implement this as a sort of pointer wrapper and overload `*` and `->` instead of using `get()`...
James McNellis
+2  A: 

I'd use the pointer holder. But if you are dead set against that, how about hiding your placement new operator=:

holder_ref& operator =(holder_ref const& other) {
    new (this) holder_ref(other);
    return *this;
}
R Samuel Klatchko
Ooh, I forgot that solution. Beautiful (by C++’ standard)! (Only, it needs the destructor call beforehand.)
Konrad Rudolph
It also needs protection against self assignment. Like Herb Sutter I really don't like this technique; it's very bad if anyone derives from holder_ref (perhaps unlikely). Also, see here: http://www.gotw.ca/gotw/023.htm
Charles Bailey
@Charles: to be fair, the points raised in GotW #23 don’t apply here (and I can actually *guarantee* that, due to constraints in the library), except for points 6 and 7 which I believe are pedantry based on mistakes in the standard (i.e. technically true to the letter but probably unintended and universally understood differently), and that are not ever relevant in practice, and for point 4, which is a bummer.
Konrad Rudolph
Correction: point 4 doesn’t concern me at all. So this solution is technically sound in my case.
Konrad Rudolph