【问题标题】:allocating memory for derived class members based on boost smart pointers in the base class through CRTP通过 CRTP 基于基类中的 boost 智能指针为派生类成员分配内存
【发布时间】:2012-07-21 18:52:55
【问题描述】:

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

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

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智能指针的操作细节有关。有谁知道如何解决这个问题?

【问题讨论】:

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


    【解决方案1】:

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

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

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

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

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

    【讨论】:

    • 感谢您的回复 - 关于 init() 函数的想法非常好。不要认为有更好的方法来做到这一点。
    • 没问题,很高兴能帮上忙。
    【解决方案2】:

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

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

    【讨论】:

    • @user1391279 实例成员偏移量在编译时是静态已知的 - 这就是您获得可行地址的原因。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-08-16
    • 2016-12-07
    • 2017-08-02
    • 2016-10-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多