【问题标题】:shared_ptr and private inheritanceshared_ptr 和私有继承
【发布时间】:2015-06-10 18:22:04
【问题描述】:

这是一个玩具示例,说明了我遇到的一个问题。该应用程序是相当不相关的(它本质上是一个元素的链接列表,最后具有特殊行为)。我无法使用派生指针构造基类 shared_ptr ,并且由于某种原因与我使用私有继承这一事实有关。

#include <iostream>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

using namespace std;

// An Item in a linked list
class A
{
public:
  //friend class B;
  typedef boost::shared_ptr<A> APtr;
  A() : next_() {}
  A(APtr n) : next_(n) {}
  APtr next() { return next_; }
  void setNext(APtr n) { next_ = n; }

  virtual void doIt() { /* standard behavior */ }  

private:
  APtr next_;
};


class B : private A // B really is a special A
                    // that should have different behavior
                    // at the tail of the chain
                    // but I want to hide A's interface
                    // to external clients
{
public:
  typedef boost::shared_ptr<B> BPtr;
  B(A::APtr prev) 
  { // Set this object as the tail
    prev->setNext(APtr(this));  /* WHY CAN'T I CONSTRUCT APtr(this) 
                                   WITH PRIVATE INH. */
  }
  void doIt() {/*special behavior at end */}

};

int main()
{
  A::APtr dummyPtr;
  A::APtr head = boost::make_shared<A>(dummyPtr);
  B::BPtr tail = boost::make_shared<B>(head);

  for(A::APtr curr = head; curr; curr=curr->next()){
    curr->doIt();
  }

  return 0;
}

我明白了

/usr/include/boost/smart_ptr/shared_ptr.hpp: In constructor ‘boost::shared_ptr<T>::shared_ptr(Y*) [with Y = B, T = A]’:
derived_shared.cpp:31:   instantiated from here
/usr/include/boost/smart_ptr/shared_ptr.hpp:352: error: ‘A’ is an inaccessible base of ‘B’

我的印象是私有继承允许派生类仍然访问基类的公共接口,但将该接口隐藏给外部客户端。为什么私有继承会导致这个错误(如果我公开继承它会起作用)?

【问题讨论】:

标签: c++ inheritance shared-ptr


【解决方案1】:

改变这一行:

prev->setNext(APtr(this)); 

到

prev->setNext(APtr(static_cast<A*>(this))); 

然后编译。

或者至少在使用std 库时是这样。 boost 通常与此类似。

还有其他错误,但会将B* 转换为A*。

为什么会这样?因为构造函数std::shared_ptr&lt;A&gt; 的模板不是你想象的那样!它更像template &lt;class X&gt; std::shared_ptr(X* v)。所以实际的B* 到A* 的演员阵容被推迟并且在非朋友成员中失败。

但是,如果您在 class B 的方法中将 B* 指针(即 this)转换为 A*(唯一一个没有 friend 声明的地方是合法的),那么您就进入了。

注意:原则上私有继承没有错。它不是反模式,提供它是有充分理由的。请考虑组合,但禁止应用程序的某些部分“访问”其“真实”类型的对象有很多用途。例如,传递一个对象 A,它有一些 B 螺栓,只有对象工厂可以访问。

PS:构造函数之所以为template&lt;class T&gt; shared_ptr&lt;T* v&gt;,是因为shared_ptr使用了传入的类型的删除器。 毫无疑问,share_ptr 巧妙地调用了“正确的”析构函数,即使它不是虚拟的。 我的“修复”实际上颠覆了这种聪明,所以要小心传递正确的删除器或(推荐)使 A 的析构函数为虚拟。

PPS:

最后是一个完全可用的程序(使用 STL。抱歉我没有 Boost):

#include <iostream>
#include <memory>

// An Item in a linked list
class A
{
public:
  //friend class B;
  typedef std::shared_ptr<A> APtr;
  A() : next_() {}
  A(APtr n) : next_(n) {}
  APtr next() { return next_; }
  void setNext(APtr n) { next_ = n;}

  virtual void doIt() { std::cout<<"normal thing"<<std::endl; }  

  virtual ~A(){}
private:
  APtr next_;
};

class B : public std::enable_shared_from_this<A>, private A // B really is a special A
                    // that should have different behavior
                    // at the tail of the chain
                    // but I want to hide A's interface
                    // to external clients
{
public:
  template<class X> friend class std::enable_shared_from_this;

  typedef std::shared_ptr<B> BPtr;


  static BPtr makeit(A::APtr prev){
    BPtr B(std::make_shared<B>());
    prev->setNext(B->shared_from_this());
    return B;
  } 

  void doIt() {std::cout<<"end thing"<<std::endl;}
private: 
  B(){}
};

int main()
{
  A::APtr dummyPtr;
  A::APtr head = std::make_shared<A>(dummyPtr);
  B::BPtr tail = B::makeit(head);

  for(A::APtr curr = head; curr; curr=curr->next()){
    curr->doIt();
  }

  return 0;
}

您需要使用enable_shared_from_this,否则您将尝试创建shared_ptr 的两个“家族”,这将不起作用。

我创建了一个工厂方法,因为修复构造函数是行不通的! enable_shared_from_this 的前提条件是必须存在 std::shared_ptr,我想这意味着“完全构造”。

以下构造函数对我不起作用:

B(A::APtr prev){
   prev->setNext(shared_from_this());
} 

也就是说,如果您确实继承自 enable_shared_from_this,最好将所有构造函数设为私有并提供返回 shared_ptr 的工厂。否则,如果调用代码本身不能确保“预先存在的shared_ptr”条件,那么您可能会陷入混乱。一个讨厌的耦合,如果有的话。

【讨论】:

    【解决方案2】:

    当您使用私有继承时,您基本上是在说“我希望 B 以 A 的形式实现,但我不希望它像 A (is-a A) 那样使用”

    在这里,你给 boost::shared_ptr 一个指向 B 的指针,就好像它是一个 A。

    这在你的设计中是矛盾的。也许将boost::shared_ptr&lt;A&gt; 声明为 B 的朋友会有所帮助,但这仍然是一个奇怪的设计。

    补充说明:如果您希望 B 成为不暴露 A 接口的特殊 A,请考虑组合而不是私有继承

    【讨论】:

    • 这是第一个想法。但是,如果将其设为受保护继承,则会得到相同的结果。一个类的成员不应该访问受保护的基吗?
    • @Christophe shared_ptr&lt;A&gt; 是一个完全独立于B 的类型。如果B 受保护地从A 继承,那么B 的所有子代也将从A 继承,但是像shared_ptr&lt;A&gt; 这样的单独类型不会知道这一点(除非您将其设为朋友)。
    • 好的,谢谢...我想我只是有点困惑,因为如果你使用原始指针(A* 和 B*),一切都会编译(即我可以做 prev->next(this) )。我不明白,因为 shared_ptr 是一个不同的对象,它无权访问 A 构造函数。但这就是原始指针能力和 shared_ptr [说没有判断...只是注意到它] 之间的不透明差异
    • @innocent_bystander shared_ptr 没有得到任何特殊待遇,它是一个普通公民。 shared_ptr&lt;A&gt; 与A 无关,同样vector&lt;int&gt; 与int 无关。将B* this 转换为A* 仅适用于B::B()。如果您要在其他任何地方这样做(即在B 的非朋友中),例如在main() 或shared_ptr&lt;B&gt; 中,它就行不通了。
    猜你喜欢
    • 2014-08-27
    • 2023-04-03
    • 2014-04-06
    • 1970-01-01
    • 1970-01-01
    • 2010-12-07
    • 2021-08-10
    • 2011-03-11
    • 2010-10-26
    相关资源
    最近更新 更多