【问题标题】:Problems writing a copy constructor for a smart pointer为智能指针编写复制构造函数时出现问题
【发布时间】:2010-10-08 08:21:34
【问题描述】:

我正在使用的代码有自己的智能指针实现,它执行简单的引用计数。是的,我们不应该有自己的实现。是的,我们应该使用 boost 中的一个或类似的。忍受我。

我发现我想写这样的代码:

...
CountedPointer<Base> base;
...
CountedPointer<Derived> derived;
...
base = derived;

但是,CountedPointer 的复制构造函数有一个这样的原型:

CountedPointer(const CountedPointer<T> &other);

所以上面的代码无法编译,因为它找不到合适的构造函数(或赋值运算符 - 那里是同样的故事)。我尝试用这样的原型重写复制构造函数:

template<U>
CountedPointer(const CountedPointer<U> &other);

但是,我遇到了一个问题,即复制构造函数必须访问它正在复制的对象的私有成员(即原始指针),并且如果它处于不同的 CountedPointer 特化中,它们是不可见的。

Alexandrescu 在他的库 Loki 中通过封装指针的访问器函数避免了这个问题,但如果可能的话,我不希望直接访问原始指针。

我有什么办法可以写这个来允许派生到基础副本,但不允许一般访问原始指针?

更新: 我已经实现了下面接受的答案,并且效果很好。当我只提供复制构造函数的模板版本,替换原始的未模板版本时,我花了一段时间弄清楚为什么我的程序会出现可怕的段错误。最终,我意识到编译器并不将模板化版本视为复制构造函数,而是提供了一个默认版本。默认的只是愚蠢地复制内容而不更新计数器,所以我最终得到了悬空指针和双重释放。同样的事情也适用于赋值运算符。

【问题讨论】:

  • 很难阻止对原始指针的访问。 &*CounterPointer() 通常是原始 T*。不过,它不适用于空指针。
  • 我觉得提供一个函数作为接口的一部分表明使用原始指针是个好主意。如果有人决定他们需要获取原始指针,他们无论如何都会修改接口。这是很小的;如果它是我的代码,它不会打扰我太多。

标签: c++ smart-pointers


【解决方案1】:

“Alexandrescu 在他的 Loki 库中通过封装指针的访问器函数避免了这个问题,但如果可能的话,我不希望直接访问原始指针”

我认为添加原始指针获取器的成本将远低于试图绕过没有原始访问的复杂性成本。只是没有一种语言机制可以在两个不相关的模板类之间转换实例。对于编译器来说,它们是两个完全不同的东西,在运行时没有任何关系。这就是为什么你的一个模板类实例不能访问其他的私有实例。

您可以考虑为所有 CountedPointers 创建与基类的这种关系。您可能必须在此基类中放置一个 void*。然后你必须自己做所有检查(T是从U派生的然后强制强制转换......我可以隐式地将T转换为U吗?如果是强制转换......等等)但这可能会变得非常复杂.这是这种方法的一个粗略的开始:

class CountedPointerBase
{
    void* rawPtr;
};

template <class T>
class CountedPointer : public ConutedPointerBase
{
      T* myRawT = reinterpret_cast<T*>(rawPtr);

      template<class U>
      CountedPointer( CountedPointer<U> u)
      {
           // Copying a CountedPointer<T> -> CountedPointer<U>
           if (dynamic_cast<U*>(myRawT) != NULL)
           {
               // Safe to copy one rawPtr to another
           }
           // check for all other conversions
      }
}

查看两种类型是否兼容可能还有很多其他复杂性。也许有一些 Loki/Boost 模板的灵巧性可以确定两个类型参数是否可以转换为另一个。

无论如何,如您所见,这可能是一个比仅添加 getter 更复杂的解决方案。能够获得原始指针还有其他好处。例如,您可以将原始指针传递给只接受原始指针的库函数,并将它们用作临时对象。我想,如果您团队中的某个人决定保留原始指针的副本而不是智能指针,那可能会很危险。那个人可能应该立即被打鳟鱼。

【讨论】:

    【解决方案2】:

    你不能让他们成为朋友吗?喜欢:

    template<typename T>
    class CountedPointer {
    
        // ...
    
        template<U>
        CountedPointer(const CountedPointer<U> &other);
    
        template<typename U> friend class CountedPointer;
    };
    

    【讨论】:

    • 这样就行了!我想知道你是否可以添加一些关于这里发生了什么的细节?例如,我不确定为什么要对朋友声明进行模板化。
    • 我不确定朋友模板声明中的 U 与复制构造函数模板声明中的 U 有什么关系
    • @Doug T. 朋友中的 U 与复制 ctor 中的 U 有点无关。我可以为朋友使用 V,它的工作原理是一样的。
    • @Ned 朋友声明必须模板化,因为类是模板化的。如果朋友没有被模板化,它会使相同的类型成为朋友,这有点没有意义:) 你需要在朋友声明中使用不同的类型(不是 T),否则它会影响“主”模板参数跨度>
    • 啊,所以您正在根据 CountedPointer 模板朋友创建所有类。
    【解决方案3】:

    如果你有一个问题,你会解决问题 计数指针(T* 其他);构造函数和类似的赋值运算符。

    【讨论】:

    • 这不是暗示 CountedPointer 可以隐式转换为 T* 吗?这是他问题的症结所在,他们真的不是......
    • 是的,这是隐含的。我认为声明运算符 T* 没有问题,而不是返回包装的指针。
    • 我不想让任何人访问原始指针。如果他们删除它怎么办?还是让它超出 CounterPointer 的生命周期?允许隐式转换也允许“删除 cp;”编译一个 CountedPointer cp - 它会在 cp 中留下一个悬空指针。
    • 好吧,如果你决定使用引用计数,你最好不要使用 delete 并且不要在堆栈上分配具有引用计数的对象。这是常识。您不会对 CoCreateInstance 创建的对象调用 delete,对吗?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-13
    • 2017-07-04
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多