Я нашел хорошее приложение для частного наследования, хотя оно имеет ограниченное использование.
Проблема, которую нужно решить
Предположим, вы получили следующий C API:
#ifdef __cplusplus
extern "C" {
#endif
typedef struct
{
/* raw owning pointer, it's C after all */
char const * name;
/* more variables that need resources
* ...
*/
} Widget;
Widget const * loadWidget();
void freeWidget(Widget const * widget);
#ifdef __cplusplus
} // end of extern "C"
#endif
Теперь ваша задача - реализовать этот API с использованием C ++.
C-ish подход
Конечно, мы могли бы выбрать стиль реализации C-ish так:
Widget const * loadWidget()
{
auto result = std::make_unique<Widget>();
result->name = strdup("The Widget name");
// More similar assignments here
return result.release();
}
void freeWidget(Widget const * const widget)
{
free(result->name);
// More similar manual freeing of resources
delete widget;
}
Но есть несколько недостатков:
- Ручное управление ресурсами (например, памятью)
- Легко настроить
struct
неправильно
- Легко забыть освободить ресурсы при освобождении
struct
- Это C-ish
C ++ Подход
Нам разрешено использовать C ++, так почему бы не использовать его полные полномочия?
Представляем автоматизированное управление ресурсами
Все вышеперечисленные проблемы в основном связаны с ручным управлением ресурсами. Решение, которое приходит на ум - наследовать от Widget
и добавить экземпляр управления ресурсами в производный класс WidgetImpl
для каждой переменной:
class WidgetImpl : public Widget
{
public:
// Added bonus, Widget's members get default initialized
WidgetImpl()
: Widget()
{}
void setName(std::string newName)
{
m_nameResource = std::move(newName);
name = m_nameResource.c_str();
}
// More similar setters to follow
private:
std::string m_nameResource;
};
Это упрощает реализацию до следующего:
Widget const * loadWidget()
{
auto result = std::make_unique<WidgetImpl>();
result->setName("The Widget name");
// More similar setters here
return result.release();
}
void freeWidget(Widget const * const widget)
{
// No virtual destructor in the base class, thus static_cast must be used
delete static_cast<WidgetImpl const *>(widget);
}
Таким образом, мы исправили все вышеперечисленные проблемы. Но клиент по-прежнему может забыть о установщиках WidgetImpl
и назначить Widget
членов напрямую.
Частное наследство выходит на сцену
Для инкапсуляции Widget
членов мы используем частное наследование. К сожалению, теперь нам нужны две дополнительные функции для приведения между двумя классами:
class WidgetImpl : private Widget
{
public:
WidgetImpl()
: Widget()
{}
void setName(std::string newName)
{
m_nameResource = std::move(newName);
name = m_nameResource.c_str();
}
// More similar setters to follow
Widget const * toWidget() const
{
return static_cast<Widget const *>(this);
}
static void deleteWidget(Widget const * const widget)
{
delete static_cast<WidgetImpl const *>(widget);
}
private:
std::string m_nameResource;
};
Это требует следующих адаптаций:
Widget const * loadWidget()
{
auto widgetImpl = std::make_unique<WidgetImpl>();
widgetImpl->setName("The Widget name");
// More similar setters here
auto const result = widgetImpl->toWidget();
widgetImpl.release();
return result;
}
void freeWidget(Widget const * const widget)
{
WidgetImpl::deleteWidget(widget);
}
Это решение решает все проблемы. Нет ручного управления памятью и Widget
хорошо инкапсулирован, так что WidgetImpl
больше не имеет открытых элементов данных. Это делает реализацию простой в использовании, правильной и сложной (невозможной?) Для неправильной.
Фрагменты кода образуют пример компиляции на Coliru .