通过CRTP基于基类中的boost智能指针为派生类成员分配内存

时间:2012-07-21 18:52:55

标签: boost shared-ptr crtp scoped-ptr static-polymorphism

这部分问题提供了背景信息,可以忽略

我正在开发一个模板库,它非常依赖于使用奇怪的重复模板模式。类结构背后的想法是用户可以

1)。使用标准方法的预定义类。这些类是基类的非常简单的叶子,它只提供构造函数/析构函数,声明变量成员并将基类声明为朋友。对派生类的变量成员进行操作的所有方法都在基类中定义。

2)。使用基类创建自己的扩展。此方法还允许用户引入自己的方法,这些方法对相同的变量成员进行操作。

设计只强制执行单级继承。

我的问题主要是关于第2节。在当前实现中,用户必须隐式定义所有构造函数(即描述类的动态变量成员的内存分配的完整过程等)。

问题

下面的示例演示了对使用CRTP提供基类构造函数中派生类的堆变量的内存分配定义的可能性的调查。

基类的一部分

template<class TLeafType, class MyClass> class sysBaseDiscreteTrajectoryPoint {
 ...

//one of the base constructors
sysBaseDiscreteTrajectoryPoint(const MyClass& MyClassInstance) {
    std::cout << "Base additional constructor called" << std::endl;
    std::cout << asLeaf().Point << std::endl;
    asLeaf().Point=new MyClass(MyClassInstance);
    std::cout << asLeaf().Point << std::endl;
}

TLeafType& asLeaf(void) {
return static_cast<TLeafType&>(*this);
}

...
};

派生类:

template<class MyClass> 
class sysDiscreteTrajectoryPoint: public sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass> {
...
friend class sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass>;
private:
    MyClass* Point;
public:
    sysDiscreteTrajectoryPoint(const MyClass& MyClassInstance): sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass>(MyClassInstance){
        std::cout << "Derived additional constructor called " << std::endl; 
        std::cout << Point << std::endl;
        std::cout << *Point << std::endl;
    }
...
}

主要:

int a(5);
sysDiscreteTrajectoryPoint<int> A(a);

代码产生以下输出:

Base additional constructor called
0x847ff4
0x8737008
Derived additional constructor called 
0x8737008
5
Derived destructor called 
Base destructor called 

输出表明这个概念可行。但是,我有两个问题。

1)。我想确保我理解在执行代码期间发生的所有进程。特别是,我对这个过程的效率感兴趣,因为我可能需要从上面提到的类中实例化大量的对象,我想了解Point会发生什么(有没有隐藏的重新定义? )

2)。这个问题与使用库boost来定义派生类成员的智能指针有关。当我尝试用boost::shared_ptr替换原始指针时,我在尝试通过基类为派生类的成员分配内存时收到了分段错误错误。代码的重要部分如下所示。

基类的一部分:

template<class TLeafType, class MyClass> class sysBaseDiscreteTrajectoryPoint {
 ...

//one of the base constructors
sysBaseDiscreteTrajectoryPoint(const MyClass& MyClassInstance) {
    std::cout << "Base additional constructor called" << std::endl;
    std::cout << asLeaf().Point << std::endl;
    asLeaf().Point.reset(new MyClass(MyClassInstance));
    std::cout << asLeaf().Point << std::endl;
}

TLeafType& asLeaf(void) {
return static_cast<TLeafType&>(*this);
}

...
};

派生类的一部分:

template<class MyClass> 
class sysDiscreteTrajectoryPoint: public sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass> {
...
friend class sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass>;
private:
    boost::shared_ptr<MyClass> Point;
public:
    sysDiscreteTrajectoryPoint(const MyClass& MyClassInstance): sysBaseDiscreteTrajectoryPoint<sysDiscreteTrajectoryPoint<MyClass>, MyClass>(MyClassInstance){
        std::cout << "Derived additional constructor called " << std::endl; 
        std::cout << Point << std::endl;
        std::cout << *Point << std::endl;
    }
...
}

主要:

int a(5);
sysDiscreteTrajectoryPoint<int> A(a);

代码产生以下输出:

Base additional constructor called
0x28d324
Segmentation fault

我也试过scoped_ptr。但是,它在运行时失败但出现了不同的错误:

Base additional constructor called
*** glibc detected *** ./TestSystem: free(): invalid pointer: 0x00d3fff4 ***
======= Backtrace: =========
/lib/i386-linux-gnu/libc.so.6(+0x6b961)[0xc4e961]
...

我认为它与boost智能指针的操作细节有关。有谁知道如何解决这个问题?

2 个答案:

答案 0 :(得分:1)

由于上述答案中给出的原因,shared_ptr地址在编译时是已知的,但shared_ptr本身仍然未初始化,因为派生类构造函数尚未被调用,因此没有机会隐式调用其实例成员的构造函数,包括shared_ptr的默认构造函数。因此,当您调用reset()来分配shared_ptr时,它首先尝试释放(并可能删除)对象,无论它在何处包含虚假地址(以避免泄漏现有的指示对象),然后再分配和引用新对象。我相信,第一步是导致段错误的原因。

如果shared_ptr构造函数首先运行,它将使其包含的原始指针为空,从而阻止后续reset()调用尝试在虚假地址释放对象。

使用asLeaf()从基类构造函数访问派生类本质上对于非POD类型是不安全的,因为构造不完整(派生类的成员尚未构造)。顺便提一下,这就是为什么来自基础构造函数的虚方法调用永远不会从更多派生类调用覆盖的原因 - 该语言明确地防止在整个对象的构造完成之前调用覆盖,因为在大多数情况下整个对象的状态是尚未定义。

可能有更好的解决方案,但是一种可行的方法是从基类的构造函数中删除该初始化代码,并将其放在init()函数中,该函数在派生的每个实例化时都显式调用类。 init()仍然可以存在于基类中,但它更安全,因为一切都会在运行时初始化。

附注:避免在没有充分理由的情况下将小对象放入shared_ptr。在这种情况下,您可能对它有合理的需求,但总的来说,我更喜欢将成员直接聚合到单所有者指针和尽可能直接指向共享指针的单所有者指针,因为开销会升级。单所有者指针涉及堆分配,共享指针还会增加计算/跟踪所有者的成本,以便在无法访问时删除该对象。

答案 1 :(得分:0)

如何从基础构造函数中访问属于派生类的Point成员?在调用基础构造函数时,派生类部分不存在。也许它只是“偶然”起作用。

但它肯定会失败shared_ptr,因为你试图在它有机会初始化之前分配它。