[英]pimpl with inheritance using smart pointer
请参阅我对 PIMPL 的继承实现。 在派生类中,DerivedImpl 继承自 BaseImpl。
问题:指向 Impl 的指针是否应该像下面的代码一样只在基类中定义? 如果是这样,每次我需要使用基指针时,我都必须将其强制转换为派生类型。 然而,根据分析结果,静态转换 shared_ptr 看起来很昂贵,因为这种转换被广泛使用。 并且 cast 函数不能在 header 中内联,因为它在那里不完整。
也许我犯了一些错误。 或者使用智能指针有更好的实现吗?
// Base.h
class BaseImpl; // pre-declaration
class Base
{
public:
Base();
explicit Base(BaseImpl* ptr);
~Base();
protected:
std::shared_ptr<BaseImpl> d_Ptr;
};
// baseimpl.h
class BaseImpl
{
double mDate;
};
// Derived.h
#include "Base.h"
class DerivedImpl;
class Derived :
public Base
{
public:
Derived();
~Derived();
std::shared_ptr<DerivedImpl> d_func();
const std::shared_ptr<DerivedImpl> d_func() const;
};
// Derived.cpp
#include "Derived.h"
#include "DerivedImpl.h"
Derived::Derived() : Base(new DerivedImpl())
{
}
Derived::~Derived()
{
}
std::shared_ptr<DerivedImpl> Derived::d_func()
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr);
}
const std::shared_ptr<DerivedImpl> Derived::d_func() const
{
return std::static_pointer_cast<DerivedImpl>(d_Ptr);
}
我假设你想要你所描述的,模实现细节:
公共类的继承层次结构。
基于实现类的相应继承层次结构。
实现对全局命名空间和/或宏的可能使用应限于单独编译的单元。
这是一个问题,派生类特定的初始化,例如在 C++ 类中包装一组低级 GUI 小部件时会弹出。 在许多其他情况下也是如此。 有多种可能的解决方案,但您目前的解决方案是通过基类构造函数向上传递一个指向实现的指针,传递到最顶层的基类,在那里它被提供给派生类。
不过,您不确定这是个好主意:
”指向Impl的指针是否应该像下面的代码那样只在基类中定义?
是的,理想情况下应该这样做,因为这种方法可确保始终完全构造可用的基类实例。 这就是 C++ 构造函数的基本思想。 在初始化(例如基类子对象)之后,您要么手头有一个工作对象,要么什么都没有,即异常或终止。
但是,这种方法可以解决两个问题:
如何高效提供派生类实现指针?
如何从基类实现派生实现?
通过为实现使用单独的头文件,可以轻松解决后一个问题。 请记住,信息隐藏的目的不是使这些类的源代码在物理上不可访问,尽管这仍然是可能的。 但要避免污染全局命名空间和宏域。
第一个问题,也就是你真正要问的问题,
”根据分析结果,静态转换 shared_ptr 看起来很昂贵,因为这种转换被广泛使用
真的不是问题。
downcast 函数只需要在代码的实现部分可以访问,并且它们的源代码是可用的并且可以内联调用。
最后,只是建议,您应该使用unique_ptr
,或者不使用智能指针,或者自动克隆智能指针,而不是shared_ptr
,作为实现指针。 因为您通常不希望公共类实例的副本与原始实例共享其实现。 除了实现没有状态的情况外,在这种情况下动态分配它不是很有意义。
例子:
#pragma once #include <memory> namespace my { using std::unique_ptr; class Base { protected: class Impl; private: unique_ptr<Impl> p_impl_; protected: auto p_impl() -> Impl* { return p_impl_.get(); } auto p_impl() const -> Impl const* { return p_impl_.get(); } Base( unique_ptr<Impl> p_derived_impl ); public: auto foo() const -> char const*; ~Base(); Base(); }; } // namespace my
#include "Base.Impl.hpp"
#include <utility> // std::move
using std::move;
using std::unique_ptr;
auto my::Base::foo() const
-> char const*
{ return p_impl()->foo(); }
my::Base::~Base() {}
my::Base::Base()
: p_impl_( new Impl() )
{}
my::Base::Base( unique_ptr<Impl> p_derived_impl )
: p_impl_( move( p_derived_impl ) )
{}
#include "Base.Impl.hpp" #include <utility> // std::move using std::move; using std::unique_ptr; auto my::Base::foo() const -> char const* { return p_impl()->foo(); } my::Base::~Base() {} my::Base::Base() : p_impl_( new Impl() ) {} my::Base::Base( unique_ptr<Impl> p_derived_impl ) : p_impl_( move( p_derived_impl ) ) {}
#include "Derived.Impl.hpp"
#include <utility> // std::move
using std::move;
using std::unique_ptr;
inline auto my::Derived::p_impl() -> Impl*
{ return static_cast<Impl*>( Base::p_impl() ); }
inline auto my::Derived::p_impl() const -> Impl const*
{ return static_cast<Impl const*>( Base::p_impl() ); }
my::Derived::~Derived() {}
my::Derived::Derived()
: Base( unique_ptr<Impl>( new Impl() ) )
{}
my::Derived::Derived( unique_ptr<Impl> p_morederived_impl )
: Base( move( p_morederived_impl ) )
{}
#pragma once #include "Base.Impl.hpp" #include "Derived.hpp" class my::Derived::Impl : public my::Base::Impl { public: auto foo() const -> char const* override { return "Derived"; } };
#include "Derived.Impl.hpp" #include <utility> // std::move using std::move; using std::unique_ptr; inline auto my::Derived::p_impl() -> Impl* { return static_cast<Impl*>( Base::p_impl() ); } inline auto my::Derived::p_impl() const -> Impl const* { return static_cast<Impl const*>( Base::p_impl() ); } my::Derived::~Derived() {} my::Derived::Derived() : Base( unique_ptr<Impl>( new Impl() ) ) {} my::Derived::Derived( unique_ptr<Impl> p_morederived_impl ) : Base( move( p_morederived_impl ) ) {}
#include "Derived.hpp" #include <iostream> using namespace std; auto main() -> int { wcout << my::Derived().foo() << endl; }
技术性:在类Derived
,向下转换函数是private
,以防止它们被更派生的类直接使用。 这是因为实现是inline
,并且应该在使用它们的每个翻译单元中定义相同。 与其将其划分为更多的头文件,更多的派生类应该/可以直接从Base
实现向下转换,就像Derived
一样。
我想你打败的目的PIMPL方法通过暴露的细节BaseImpl
到的用户Base
,而不是仅仅从派生类Base
,而是所有的用户Base
一般。 出于完全相同的原因, DerivedImpl
也需要隐藏。
我推荐以下内容:
// Base.h
class Base
{
public:
Base();
virtual ~Base();
// Add copy constructor and copy assignment operator too.
// Follow the rule of Three/rule of Five.
// Class that holds the implementation details of Base.
class Impl;
private:
// Never expose the details of Impl
// and never expose d_Ptr to clients.
Impl* d_Ptr;
};
// Base.cpp
class Base::Impl
{
// Add the necessary member variables and functions to facilitate
// Base's implementation
};
Base() : d_Ptr(new Impl)
{
}
~Base()
{
delete d_Ptr;
}
// Derived.h
#include "Base.h"
class Derived : public Base
{
public:
Derived();
~Derived();
// Add copy constructor and copy assignment operator too.
// Follow the rule of Three/rule of Five.
// Class that holds the implementation details of Derived.
// Has no relation to Base::Impl
class Impl;
private:
// Never expose the details of Impl
// and never expose d_Ptr to clients.
Impl* d_Ptr;
};
// Derived.cpp
class Derived::Impl
{
// Add the necessary member variables and functions to facilitate
// Derived's implementation
};
Derived() : d_Ptr(new Impl)
{
}
~Derived()
{
delete d_Ptr;
}
老式的讨论,但是.. 我一直在思考在类的层次结构中使用 pimpl 成语的想法,但我不明白它如何在不向 impls 添加多态行为的情况下变得实用。 鉴于 pimpl 成语的动机之一是避免 vtable,似乎一旦将多态性添加到 impls,您就不再使用经典意义上的 pimpl 成语。 相反,它现在更像是一种桥接模式。 它可以解决类似的问题,但其动机略有不同。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.