выделение памяти для членов производного класса на основе интеллектуальных указателей повышения в базовом классе через CRTP

Эта часть вопроса содержит справочную информацию и может быть проигнорирована

Я работаю над библиотекой шаблонов, которая в значительной степени зависит от использования любопытно повторяющегося шаблона шаблона. Идея структуры класса заключается в том, что пользователь может либо

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 smart pointers. Кто-нибудь знает, как решить эту проблему?


person Community    schedule 21.07.2012    source источник


Ответы (2)


адрес shared_ptr известен во время компиляции по причинам, указанным в приведенных выше ответах, но сам shared_ptr по-прежнему не инициализирован, поскольку конструктор производного класса еще не был вызван и, следовательно, не имел возможность неявно вызывать конструкторы членов своего экземпляра, включая конструктор по умолчанию для shared_ptr. Поэтому, когда вы вызываете reset() для назначения shared_ptr, он сначала пытается освободить (и, возможно, удалить) объект по любому ложному адресу, который он содержит (во избежание утечки существующего референта), прежде чем присваивать и ссылаться на новый объект. Я считаю, что этот первый шаг и является причиной segfault.

Если конструктор shared_ptr запускается первым, он обнуляет содержащийся в нем необработанный указатель, предотвращая попытку последующего вызова reset() освободить объект по ложному адресу.

Использование asLeaf() для доступа к производному классу из конструктора базового класса по своей сути небезопасно для типов, отличных от POD, поскольку построение не завершено (члены производного класса еще не созданы). Кстати, именно поэтому вызовы виртуальных методов из базового конструктора никогда не будут вызывать переопределения из более производных классов — язык явно предотвращает вызов переопределений до тех пор, пока не будет завершено создание всего объекта, потому что в большинстве случаев состояние всего объекта равно еще не определено.

Для вас могут быть лучшие решения, но один подход, который будет работать, состоит в том, чтобы удалить этот код инициализации из конструктора базового класса и поместить его в функцию init(), которая вызывается явно при каждом создании экземпляра производного класса. init() все еще может жить в базовом классе, но это безопаснее, потому что к моменту запуска все уже будет инициализировано.

Примечание: не помещайте мелкие предметы в shared_ptr без уважительной причины. В этом случае у вас может быть законная потребность в этом, но в целом я предпочитаю прямое агрегирование членов указателям с одним владельцем и указатели с одним владельцем общим указателям, где это возможно, потому что накладные расходы возрастают. Указатели с одним владельцем включают выделение кучи, а общие указатели также добавляют к этому стоимость подсчета/отслеживания владельцев, чтобы объект можно было удалить, когда он недоступен.

person TheJim    schedule 31.07.2012
comment
Спасибо за ответ - идея с функцией init() действительно хороша. Не думайте, что есть лучший способ сделать это. - person ; 31.07.2012
comment
Нет проблем, рад, что смог помочь. - person TheJim; 31.07.2012

Как возможно, что вы можете получить доступ к члену Point, принадлежащему производному классу, из базового конструктора? Когда вызывается базовый конструктор, часть производного класса не существует. Возможно, это срабатывает просто «случайно».

Но это, безусловно, терпит неудачу с shared_ptr, потому что вы пытаетесь присвоить его до того, как он сможет инициализироваться.

person Igor R.    schedule 21.07.2012
comment
@ user1391279 смещения членов экземпляра известны статически во время компиляции, поэтому вы получаете возможный адрес. - person Igor R.; 22.07.2012