Reputation: 319
I have some class which manages some resources. You get get a reference to one of those resources from this managing class. Resources are heavy-weight objects, so you normally don't want to copy them. In my case, a base-class for resources is no solution, because my 'resource-manager' is a template class which should work with already defined resource classes of other people. I made some simple example to show my problem:
#include <iostream>
// copyable class
class Copyable {
public:
Copyable() = default;
Copyable(const Copyable&) {
std::cout << "Copyconstructor called" << std::endl;
}
Copyable& operator=(const Copyable&) {
std::cout << "assignment operator called" << std::endl;
return *this;
}
};
// non copyable class
class NonCopyable {
public:
NonCopyable() = default;
private:
NonCopyable(const NonCopyable&) = delete;
NonCopyable& operator=(const NonCopyable&) = delete;
};
// some class that can return a reference
template <typename T>
class SomeHolder {
private:
T some_member;
public:
T& getReference() {
return some_member;
}
};
int main() {
SomeHolder<Copyable> holder_1;
auto copyable_1 = holder_1.getReference();
auto ©able_2 = holder_1.getReference();
SomeHolder<NonCopyable> holder_2;
//auto noncopyable_1 = holder_2.getReference(); // line 39
auto &noncopyable_2 = holder_2.getReference(); // line 40
}
The class SomeHolder returns a reference on the owned object. As you see in line 39, this reference gets copied. Normal way to get the reference is done in line 40. But if you miss the & you get a copy, which you normally don't want. If you uncomment line 39, you get an error because the resource isn't copyable in this case. Like i said before, wanting all resources to be not copyable isn't a solution in my case. Do you have ideas to other design decisions? I write this class to hold resources for games. I am writing a small library for recurring tasks. So i don't know if your framework works with copyable textures and sounds. Maybe there is no great solution for my problem, but if you have design suggestions, let me know.
Upvotes: 0
Views: 1018
Reputation: 27028
I also suggest to use a smart pointer, but I prefer to not use reference members. That way client can keep handles.
template<typename T>
struct ViewPtr {
ViewPtr() = delete;
ViewPtr(T* tp) : tp(tp) {}
T* operator->() { return tp; }
const T* operator->() const { return tp; }
private:
T* tp;
};
Usage:
template <typename T>
class SomeHolder {
private:
T some_member;
public:
ViewPtr<T> getReference() {
return &some_member;
}
};
This Viewtr
also opts to "propagate const". That is: if you have a const view object (or if it's a member, and you are in a const method) you get a const interface.
Upvotes: 1
Reputation: 238351
You cannot prevent other code from copying objects that are copyable. So, if you want other code to not be able to copy copyable objects that you hold, then you must not return a reference to them.
Instead, you could return a wrapper object, that delegates its functionality to an instance of the copyable class whose reference is held privately within the wrapper:
template <typename T>
class Wrapper {
T& t;
public:
Wrapper(T& t) : t(t) {}
T* operator->() { return &t; }
};
Wrapper<T> SomeHolder<T>::getSomeMember() {
return {some_member};
}
Since direct member access operator cannot be overloaded, you will need to change the calling code to use the indirect member access instead.
Upvotes: 3
Reputation: 21514
If you make your objects copyable there will definitely be no way for your to prevent them from copying it....
If you return a reference to a copyable object, you can't prevent developers using your class from copying them at some point. If they use auto&
, the object won't be copied, but if they use ̀auto
, the object will be copied....and even if they used auto&
when they called your getReference()
function, you can't prevent them from doing a later copy (when they will call a function or assigning a new variable).
An alternative, as commented by "apple apple", is to return a shared_ptr
. Then your ressource manager stores shared pointers to ressources and gives access to them. Then, the caller may use many instances of the shared_ptr
, all pointing to the same ressource which is not copied in the end. If he uses auto
he gets a copy of the shared_ptr
, if he uses auto&
he gets a reference to shared_ptr
, but, in both case they point to the same ressource which were not copied.
If the caller really wants to do a copy, he will find a way to do it by using shared_ptr::get()
to access the copyable object.
Upvotes: 0