Как вызвать конструктор не по умолчанию для каждого унаследованного типа из списка типов?

Я использую список типов 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;
};

boost::mpl::for_each Алгоритм выглядит многообещающе, но я не могу понять, как использовать его для решения этой проблемы.


person Steve Guidi    schedule 04.11.2009    source источник
comment
Вы не можете определить Host таким образом - поскольку inherit_linearly строит цепочку наследования, вам придется каким-то образом связать вызовы конструктора, т.е. позволить Host вызывать его для наиболее производного, наиболее производного для следующего и т. д.   -  person Georg Fritzsche    schedule 04.11.2009


Ответы (4)


Я не мог устоять перед искушением посмотреть, как это можно сделать с помощью 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;
};

произойдут плохие вещи, так как копирующий элемент ExpensiveType будет вызываться с неинициализированным объектом.

person Éric Malenfant    schedule 04.11.2009
comment
Отличный момент для вызова копировщика неинициализированного типа. Лучше перестраховаться и вместо этого передать указатель. - person Steve Guidi; 04.11.2009
comment
@Steve: Ну, передача указателя на неинициализированный тип не сильно отличается. Это может быть более сильным указанием получателю на то, что объект имеет ссылочную (а не значение) семантику и предотвращает ошибки, которые я показываю в своем ответе, но на практике получатель указателя может делать что угодно с указанным - возражать, как и со ссылкой. - person Éric Malenfant; 05.11.2009
comment
Как человек, который просто изучает этот материал, я действительно хотел бы, чтобы вы объяснили, почему это работает. Это может быть очень просто, но тогда это всего лишь небольшое описание. - person Narfanator; 26.02.2010
comment
@Narfanator: я не уверен, что вы на самом деле ищете, но позвольте мне попробовать. Скажите, пожалуйста, отвечает ли это на вопрос: inherit_linearly<Types, Node, Root>::type можно рассматривать как аналог std::accumulate: он принимает последовательность (типы) и начальное значение (корень) и возвращает результат последовательного применения двоичной функции (узел) к каждому элементу в последовательности и на накопленном результате предыдущих вызовов. (продолжение в следующем комментарии)... - person Éric Malenfant; 26.02.2010
comment
... (продолжение предыдущего комментария) Чтобы упростить задачу, рассмотрим последовательность из 3 элементов, seq. std::accumulate(seq.begin(), seq.end(), init, fun) возвращает fun(fun(fun(init, i0), i1), i2) (где iN обозначает значение Nth элемента в seq). inherit_linearly похож, но он вычисляет типы вместо значений, работает во время компиляции, а не во время выполнения, и вызывается при просмотре вложенного типа typedef. inherit_linearly<vector<A, B, C>, Fun<_1, _2>, Empty>::type таким образом: Fun<Fun<Fun<Empty, A>, B>, C>. (продолжение в следующем комментарии)... - person Éric Malenfant; 26.02.2010
comment
... (продолжение предыдущего комментария) Обратите внимание, что результирующий тип не наследуется напрямую от A, B и C. Он наследуется от C и от типа, который, в свою очередь, наследуется от B и от типа, наследуемого от A и Пустой. Теперь хитрость для достижения того, о чем просит OP, состоит в том, чтобы определить Fun‹X, Y› так, чтобы он наследовался как от X, так и от Y, и чтобы его ctor инициализировал свои базы X и Y по желанию. Это то, что делает PolicyWrapper. - person Éric Malenfant; 26.02.2010

Если вам нужна такая генерация, я могу только порекомендовать прочитать Современный дизайн C++. Существует целая глава, посвященная генерации иерархии из списка типов. Вы также можете найти его на веб-сайте Loki: Генераторы иерархии; хотя вам будет не хватать диаграмм и объяснений, а также самого процесса.

Для вашей конкретной проблемы это кажется довольно простым.

// 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, единственное, что нужно понимать, это то, что вы не можете сделать это с большим однослойным MI, но вы можете добавить много слоев.

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

Также обратите внимание, что этот подход может быть адаптирован для непосредственного использования mpl::vector, но это будет включать использование операций программирования меташаблонов: back, pop_back и empty, по крайней мере, которые могут больше запутать код, чем на самом деле помочь.

person Matthieu M.    schedule 04.11.2009
comment
Подход с двойным MI определенно делает его чище, поскольку он не смешивает семантику деривации с политиками. - person Georg Fritzsche; 04.11.2009
comment
Похоже, struct SH: Head<Tail> должно быть struct SH : Head, SH<Tail>? - person Georg Fritzsche; 04.11.2009

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

Я набросал базовый пример, но в итоге не использовал 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);
}
person Georg Fritzsche    schedule 04.11.2009
comment
Идея проста и эффективна, хотя использование головы кажется немного неудобным. Имхо не совсем отполировано. - person Matthieu M.; 04.11.2009
comment
Я знаю, просто быстрый подход, чтобы донести идею. - person Georg Fritzsche; 04.11.2009

Создайте параметризованный конструктор и передайте ему параметры. Таким образом, вы можете достичь двух целей одновременно. 1) Перегрузка конструктора 2) Избегайте вызова конструктора по умолчанию.

person Sachin Chourasiya    schedule 04.11.2009
comment
Вопрос не в базовом использовании конструктора. - person Georg Fritzsche; 04.11.2009