防止复制返回的引用

时间:2018-04-04 14:15:28

标签: c++ c++11

我有一些管理一些资源的课程。您可以从此管理类中获取对其中一个资源的引用。资源是重量级的对象,因此您通常不想复制它们。就我而言,资源的基类不是解决方案,因为我的资源管理器'是一个模板类,它应该与已定义的其他人的资源类一起使用。我举了一些简单的例子来说明我的问题:

#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 &copyable_2 = holder_1.getReference();

    SomeHolder<NonCopyable> holder_2;
    //auto noncopyable_1 = holder_2.getReference(); // line 39
    auto &noncopyable_2 = holder_2.getReference();  // line 40
}

SomeHolder类返回对所拥有对象的引用。如第39行所示,此引用被复制。获得参考的正常方式是在第40行完成的。但如果你错过了&amp;你得到一份你通常不想要的副本。如果取消注释第39行,则会出现错误,因为在这种情况下资源不可复制。 就像我之前说过的那样,想要所有资源都不可复制在我的案例中不是一个解决方案。您对其他设计决策有什么想法吗?我写这个课程来保存游戏资源。我正在为重复的任务编写一个小型库。所以我不知道你的框架是否适用于可复制的纹理和声音。也许我的问题没有很好的解决方案,但如果你有设计建议,请告诉我。

3 个答案:

答案 0 :(得分:3)

您无法阻止其他代码复制可复制的对象。因此,如果您希望其他代码无法复制您拥有的可复制对象,则不得返回对它们的引用。

相反,您可以返回一个包装器对象,该对象将其功能委托给可复制类的实例,该实例的引用在包装器中私有保存:

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

由于直接成员访问运算符无法重载,因此您需要更改调用代码以改为使用间接成员访问。

答案 1 :(得分:1)

我还建议使用智能指针,但我更喜欢不使用引用成员。这样客户端可以保留句柄。

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

用法:

template <typename T>
class SomeHolder {
private:
  T some_member;
public:
  ViewPtr<T> getReference() {
    return &some_member;
  }
};

这个Viewtr也选择“传播const”。那就是:如果你有一个const视图对象(或者如果它是一个成员,并且你是一个const方法),你会得到一个const接口。

答案 2 :(得分:0)

如果你的对象是可复制的,你肯定无法阻止它们复制它....

如果返回对可复制对象的引用,则无法阻止使用您的类的开发人员在某些时候复制它们。如果他们使用auto&,则不会复制该对象,但如果他们使用auto,则会复制该对象....即使他们在调用时使用了auto&您的getReference()函数,您无法阻止它们执行以后的复制(当它们调用函数或分配新变量时)。

“苹果苹果”评论的替代方案是返回shared_ptr。然后,您的资源管理器存储指向资源的共享指针并提供对它们的访问权限。然后,调用者可以使用shared_ptr的许多实例,所有实例都指向最终未复制的相同资源。如果他使用auto,他会获得shared_ptr的副本,如果他使用auto&,则会获得对shared_ptr的引用,但在这两种情况下,他们都指向同一个资源没有被复制。

如果调用者真的想要复制,他会找到一种方法来使用shared_ptr::get()来访问可复制对象。