Как мне вызвать конструктор не по умолчанию для каждого унаследованного типа из списка типов?
-
13-09-2019 - |
Вопрос
Я использую список типов boost для реализации шаблона политики следующим образом.
using namespace boost::mpl;
template <typename PolicyTypeList = boost::mpl::vector<> >
class Host : public inherit_linearly<PolicyTypeList, inherit<_1, _2> >::type
{
public:
Host() : m_expensiveType(/* ... */) { }
private:
const ExpensiveType m_expensiveType;
};
Тот самый Host
класс знает, как создать экземпляр ExpensiveType
, что является дорогостоящей операцией, и каждый класс политики предоставляет функциональные возможности для ее использования.Класс политики всегда будет минимально иметь конструктор, определенный в следующем примере политики.
struct SamplePolicy
{
SamplePolicy(const ExpensiveType& expensiveType)
: m_expensiveType(expensiveType) { }
void DoSomething()
{
m_expensiveType.f();
// ...
}
private:
const ExpensiveType& m_expensiveType;
};
Можно ли определить конструктор Host
таким образом, чтобы вызвать конструктор каждой заданной политики?Если список типов не был задействован, это очень просто, поскольку тип каждой политики явно известен.
template <typename PolicyA, typename PolicyB>
class Host : public PolicyA, public PolicyB
{
public:
Host() :
m_expensiveType(/* ... */),
PolicyA(m_expensiveType),
PolicyB(m_expensiveType) { }
private:
const ExpensiveType m_expensiveType;
};
Тот самый повышение::mpl:: для каждого алгоритм выглядит многообещающим, но я не могу взять в толк, как его использовать для решения этой проблемы.
Решение
Я не мог устоять перед искушением посмотреть, как это можно сделать с помощью inherit_linearly
.Оказывается, все не так уж плохо, ИМХО:
template<class Base, class Self>
struct PolicyWrapper : Base, Self
{
PolicyWrapper(const ExpensiveType& E)
: Base(E), Self(E)
{}
};
struct EmptyWrapper
{
EmptyWrapper(const ExpensiveType& E)
{}
};
template <typename PolicyTypeList = boost::mpl::vector<> >
class Host :
public inherit_linearly<
PolicyTypeList,
PolicyWrapper<_1, _2>,
EmptyWrapper
>::type
{
typedef typename inherit_linearly<
PolicyTypeList,
PolicyWrapper<_1, _2>,
EmptyWrapper
>::type BaseType;
public:
Host() : BaseType(m_expensiveType)
{}
private:
const ExpensiveType m_expensiveType;
};
Однако это предупреждение:Передача ссылки на неинициализированный элемент, подобный тому, что делается в Host ctor, очень хрупка.Если, например, кто-то напишет Политику, подобную этой:
struct BadPolicy
{
BadPolicy(const ExpensiveType& E)
: m_expensiveType(E)
{}
ExpensiveType m_expensiveType;
};
произойдут плохие вещи, так как ctor копирования ExpensiveType будет вызван с неинициализированным объектом.
Другие советы
Если вам нужно такое поколение, я могу только порекомендовать почитать книгу Александреску. Современный дизайн на C ++.Существует целая глава, посвященная созданию иерархии из списка типов.Вы также можете найти его на веб-сайте Локи: Генераторы иерархии;хотя вы будете скучать по диаграммам и пояснениям, а также по самому процессу.
Для вашей конкретной проблемы это кажется довольно простым.
// Helper
struct nil
{
};
template < class Head, class Tail = nil>
struct SH: Head<Tail> /* for SimpleHierarchy */
{
SH(const ExpensiveType& e): Head(e), SH<Tail>(e) {}
};
template<>
struct SH<nil,nil>
{
SH(const ExpensiveType& e) {}
}:
// Policies
class A
{
public:
A(const ExpensiveType& e) : T(e), m_e(e) {}
private:
const ExpensiveType& m_e;
};
class B
{
public:
B(const ExpensiveType& e) : T(e), m_e(e) {}
private:
const ExpensiveType& m_e;
};
class C
{
public:
C(const ExpensiveType& e) : T(e), m_e(e) {}
private:
const ExpensiveType& m_e;
};
// Use
// nesting example
typedef SH<A, SH<B,C> > SimpleHierarchy;
// Your example, revisited
template <class A, class B>
class Host: SH<A,B>
{
public:
Host(const ExpensiveType& e): SH<A,B>(e), m_e(e) {}
private:
const ExpensiveType& m_e;
};
Конечно, это всего лишь набросок.Основная проблема здесь - это расширяемость.Если вы прочтете книгу Александреску, вы узнаете гораздо больше, а если у вас нет времени, ознакомьтесь с исходным кодом, возможно, это именно то, что вам нужно.
Есть способы сделать это непосредственно из mpl::vector
, единственное, что нужно понимать, это то, что вы не можете сделать это с помощью big MI single layer, но вы можете добавить много слоев.
Здесь я решил не добавлять сложности на уровне Политик (они не шаблонизированы) и вместо этого полагаться на MI (dual) на каждом уровне.Вы могли бы сделать это чисто линейным, но шаблонизация ваших политик означает, что вы не можете определить их в исходном файле.
Также обратите внимание, что этот подход может быть адаптирован для принятия mpl::vector
напрямую, но это потребовало бы использования операций программирования на основе меташаблонов: back
, pop_back
и empty
по крайней мере, это может больше запутать код, чем помочь на самом деле.
Как упоминалось в комментарии, вам нужно связать вызовы конструктора по цепочке.Для этого каждый тип в цепочке вывода должен знать, от какого типа он получен - чтобы обеспечить возможность произвольных последовательностей вывода, нам нужно создать шаблоны этих типов, чтобы их базой мог быть любой тип.
Это позволяет нам ссылаться на базу и явно вызывать ее конструкторы.
Я набросал базовый пример, но в итоге не использовал boost, потому что mpl::vector
ожидает известные типы, и мне нужно было передать ему параметры шаблона шаблона.Вместо этого я использовал пользовательский список типов, который поддерживает параметры шаблона шаблона и неявно выводится.
struct expensive {};
// derivation list
struct nil {}; // list end
struct Noop { // do nothing on end of derivation chain
Noop(expensive& e) {}
};
template<template <typename T> class H, typename L>
struct DL {
typedef L tail;
typedef H<typename tail::head> head;
};
template<template <typename T> class H>
struct DL<H, nil> {
typedef H<Noop> head;
};
// example types
template<class T>
struct A : T {
A(expensive& e) : T(e) {}
};
template<class T>
struct B : T {
B(expensive& e) : T(e) {}
};
// derivation chain usage example
typedef DL<A, DL<B, nil> > DerivationChain;
class User : DerivationChain::head
{
public:
User(expensive& e) : DerivationChain::head(e) {}
};
int main(int argc, char** argv)
{
expensive e;
User u(e);
}
Создайте параметризованный конструктор и передайте ему параметры.Таким образом, вы могли бы достичь двух целей одновременно.1) Перегрузка конструктора 2) Избегайте вызова конструктора по умолчанию.