0

I have the following problem

Buffer buffer1;
Buffer buffer2;

std::unique_ptr<const Buffer> hotBuffer;
std::unique_ptr<Buffer> backBuffer;

I want to fill the back buffer while the hot buffer is being read, and want the hot buffer to be un-writable and const for this reason, but at some point after it has been read and the back buffer filled I want to swap these buffers over. These buffers are quite large so I want them to not be copied, hence the unique_ptr swapping.

I want to do something along the lines of:

hotBuffer.swap(backBuffer);

but cant seem to get it to work no matter what kind of const_cast variations I try...

Is there a solution or is this insanity?

Jay
  • 2,553
  • 3
  • 17
  • 37
  • 1
    You should probably read [this answer](https://stackoverflow.com/a/19554871/6486738). It is undefined behavior to cast a const variable. – Ted Klein Bergman Nov 30 '20 at 02:36
  • 2
    @TedKleinBergman I thought this was only if the underlying variable is const? My actual buffers are non-const, only the pointers – Jay Nov 30 '20 at 02:49
  • I get that you want the back buffer to replace the hot buffer, updating what is read. Why do you want the hot buffer to replace the back buffer? Is it just to avoid a de-allocation followed by an allocation of the same size? – JaMiT Nov 30 '20 at 03:54
  • Where do `hotBuffer` and `backBuffer` live? Are they part of an object or are they global variables? (If they are not part of an object, why are they not part of the object that fills the back buffer?) – JaMiT Nov 30 '20 at 03:56
  • 1
    `swap` method probably won't work, but you could swap raw pointers manually, via `release`. Something like `Buffer* hotRaw = const_cast(hotBuffer.release()); Buffer* backRaw = backBuffer.release(); hotBuffer = backRaw; backBuffer = hotRaw;` – Igor Tandetnik Nov 30 '20 at 04:46
  • 1
    Yes, it's only an issue when the underlying variable is const. So doing a const_cast on a const pointer to a non-const value is fine. – Ted Klein Bergman Nov 30 '20 at 19:09

2 Answers2

2

To swap, you need compatible const qualification because otherwise a pointer to const would need to be cast to a pointer to non-const. To get compatible const qualification, you have to create a temporary and cast as Igor's comment suggests.

So... something like this:

template<typename U, typename T>
std::unique_ptr<U> const_pointer_cast(std::unique_ptr<T>&& ptr) {
    return std::unique_ptr<U>(const_cast<U*>(ptr.release()));
}

void swap_buffers(std::unique_ptr<const Buffer>& hotBuffer, std::unique_ptr<Buffer>& backBuffer) {
    backBuffer = const_pointer_cast<Buffer>(std::exchange(hotBuffer, std::move(backBuffer)));
}
Jeff Garrett
  • 5,863
  • 1
  • 13
  • 12
2

I want to fill the back buffer while the hot buffer is being read, and want the hot buffer to be un-writable and const for this reason,

So you decided to change your implementation to enforce your desired interface. Often, this is a sub-optimal approach.

Better would be to encapsulate more. I would re-imagine these variables as a class, where internally both buffers are writable, but external code sees the hot buffer as const. (If these variables are already inside a class, re-imagine them as a nested class.) This would be a small class with a single, focused purpose – controlling which region of memory is considered "hot" and which is "back". For example:

// Not sold on the name, but it will do for this demonstration
class BufferManager {
public:
    // Instead of access to the smart pointers, provide direct access to the buffers.
    // (Hide the detail of where these buffers live.)
    const Buffer & hotBuffer() const { return *hot; }
    Buffer &       backBuffer()      { return *back; }
    
    // A way for external code to say it is time to swap buffers:
    void flip() { hot.swap(back); }

    // Placeholder constructor; modify to suit your needs:
    BufferManager() : hot(std::make_unique<Buffer>()),
                      back(std::make_unique<Buffer>())
    {}

private:
    std::unique_ptr<Buffer> hot;
    std::unique_ptr<Buffer> back;
};

With this approach, both smart pointers point to something modifiable, so there is no longer a problem swapping them. Anything outside this class, though, sees the hot buffer as const.

You might want to play with the names a bit. (Since you'll need a name for the BufferManager variable, it might be enough to name the accessor functions hot and back, which of course means renaming the private data members.) The interface might need refining. The main point is that you add a layer of abstraction so that the interface applies const when appropriate, not the implementation.

Note: It might be appropriate to incorporate the code that fills the back buffer into this class. Perhaps both the back buffer and flip() could be made private. A key thing to keep in mind is that nothing in this class should be interested in reading the hot buffer. That is for outside code to deal with. If this is done, the purpose of this class morphs into providing the hot buffer.

JaMiT
  • 14,422
  • 4
  • 15
  • 31
  • Other answer accepted because it answers the general question better, but upvoted because this is a good solution to my specific problem, thanks! – Jay Dec 04 '20 at 19:15