2015-05-18 3 views
7

Что плохого в том, чтобы делать что-то подобное?расширение shared_ptr по наследованию

class myclass : public std::shared_ptr<myotherclass> { 
    // some code, an allocation is never done 
    std::string get_info() { 
      if(*this != nullptr) return "<info>" + (this->info * 3) + "</info>"; 
      else return ""; 
    } 
}; 

Если в классе не предусмотрено распределение, это просто необходимо сделать некоторое украшение, как указано выше?

+2

Что такого хорошего в том, чтобы делать что-то подобное? – Drax

+0

В большинстве случаев вы должны иметь возможность описать отношения наследования с английской фазой «есть». Итак, в приведенном выше случае вы говорите: myclass "является" std :: shared_ptr . Это, вероятно, не то, что вы имеете в виду. –

+0

Это не «плохо», а «странно». Разумеется, это должно быть членом самого «myotherclass» (или, возможно, не являющегося членом, действующим на «myotherclass»), а не что-то, связанное с определенным типом интеллектуального указателя? –

ответ

7

В принципе разрешено выходить из классов STL, см. here и here. Однако вы должны знать, что в этом случае вы не должны работать с указателем на базовый класс, то есть std::shared_ptr<myotherclass>*.

Так что это и их варианты должны быть запрещены:

std::shared_ptr<myotherclass>* ptr = new myclass(/* ... */); 

... но согласился, что выглядит немного синтетику.

Почему это запрещено? Поскольку классы STL не имеют виртуального деструктора. Поэтому, когда вы хотите, чтобы ваш выделенный класс delete, производная часть остается. Это, в свою очередь, вызывает undefined behaviour и, возможно, создает утечку памяти, даже если у вас нет определенных распределений в производном классе.

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

class myclass : private std::shared_ptr<myotherclass> {}; 
       ^^^^^^^ 

Это, однако, может привести к проблемам с бинарной совместимости см комментарии к this answer.

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

+1

'std :: shared_ptr * ptr = ...' ерунда. Если виртуальный деструктор становится необходимым для класса RAII, что-то уже сильно злоупотребляло. – Potatoswatter

+0

@davidhigh - очень полный ответ: показывает пределы юзабилити («вы не должны работать с указателем на базовый класс»), указывает на ссылки (в одном из них я мог видеть, где именно в спецификации, которую я делал ошибку) , и демонстрирует понимание того, что пытался создать создатель вопроса («выставлять требуемую функциональность (с недостатком, который вам иногда приходится выставлять много), или вы можете настроить автономную функцию»). – ribamar

+0

@Potatoswatter: 'std :: shared_ptr * ptr = ...' * * * ерунда, конечно ... и на самом деле так абсурдно, что никто никогда не сделает это по ошибке. – davidhigh

1

Поскольку вы никогда не будете вручную delete (и вы никогда не должны вручную delete ничего, что скорее является точкой shared_ptr), виртуальные деструкторы на самом деле не являются проблемой.

Некоторые проблемы с совместимостью могут возникнуть.

  1. Вы получаете свой производный класс только при создании определенных его экземпляров. Когда вы получите shared_ptr откуда-то вроде get_shared_from_this, он не включит ваш info.

  2. Шаблоны функций, перегруженные на shared_ptr<T>, не наследуют наследство. Ваш производный класс внезапно окажется чуждым случайным функциям, таким как std::static_pointer_cast.

К счастью, стандартная библиотека C++ полна аккуратных крючков расширяемости.Вы можете установить пользовательский Deleter так:

template< typename t > 
struct my_deleter 
    : std::default_delete<t> { 
    std::string info; 

    my_deleter(std::string in_info) 
     : info(std::move(in_info)) {} 
}; 

std::shared_pointer<foo> myfoo(new foo, my_deleter{ "it's a foo" }); 

и получить информацию с помощью функции, не являющихся членами:

template< typename t > 
std::string get_my_info(std::shared_ptr<t> ptr) { 
    my_deleter<t> * dp = std::get_deleter< my_deleter<t> >(ptr); 
    if (! dp) return {}; 
    return dp->info; 
} 

Это не очень хорошей программной архитектуры, так как есть только один заказ Deleter слот на общий объект. Тем не менее, он может сделать это.

Смежные вопросы