LINUX.ORG.RU

[C++] Как бы вы оформили такое?

 


0

0

Сейчас имеется нечто вроде:

Object_Ptr create_object(const std::string& host, uint16_t port)
{
#ifdef WITH_PROTOCOL_A
    try {
        return Object_Ptr(new Object_A(host, port));
    } catch (const Protocol_Exception&) {
        // nil
    }
#endif// WITH_PROTOCOL_A

#ifdef WITH_PROTOCOL_B
    try {
        return Object_Ptr(new Object_B(host, port));
    } catch (const Protocol_Exception&) {
        // nil
    }
#endif// WITH_PROTOCOL_B

    return Object_Ptr();
}
В принципе, меня такое устраивало, но таких WITH_PROTOCOL_X уже около десятка, и будет больше. Есть ли какие-то общепринятые методы, идиомы и т.д., чтобы покрасивее такое сделать?


Object_Ptr create_object(const std::string& host, uint16_t port) 
{ 
    try { 
#ifdef WITH_PROTOCOL_A 
        return Object_Ptr(new Object_A(host, port)); 
#endif// WITH_PROTOCOL_A 
#ifdef WITH_PROTOCOL_B 
        return Object_Ptr(new Object_B(host, port)); 
#endif// WITH_PROTOCOL_B 
    return Object_Ptr(); 
    } catch (const Protocol_Exception&) { 
        // nil 
    } 
} 
namezys ★★★★
()
Ответ на: комментарий от namezys

Нет, тут как раз смысл в том, чтобы попробовать разные протоколы для хоста и найти (ну, или не найти) подходящий. То есть перехватывать Protocol_Exception нужно для каждого Object_X, и пробовать следующий.

undet
() автор топика
Ответ на: комментарий от one_more_hokum

Избавит ли меня это от повторения одного и того-же кода?

template <class T>
Object_Ptr
magic_function(const std::string& host, uint16_t port)
{
    try {
        return Object_Ptr(new T(host, port));
    } catch (const Protocol_Exception&) {
        // nil
    }

    return Object_Ptr();
}

Object_Ptr create_object(const std::string& host, uint16_t port)
{
    Object_Ptr result;

    if (result == 0) {
        result = magic_function<Object_A>(host, port);
    }

    if (result == 0) {
        result = magic_function<Object_B>(host, port);
    }

    if (result == 0) {
        result = magic_function<Object_C>(host, port);
    }

    return result;
}
Это выглядит чуть чише и проще, но не слишком сильно. Или про что ты говорил?

undet
() автор топика
Ответ на: комментарий от namezys

Эти #define'ы для опций сборки. У меня библиотека может быть собрана с поддержкой разных протоколов.

undet
() автор топика

А что насчёт

#ifdef WITH_PROTOCOL_B 
#define Obj_X Object_B
#endif// WITH_PROTOCOL_B 

А дальше один код на всё? Емнип, именно так это и делается

name_no ★★
()
Ответ на: комментарий от namezys

Я пока придумал нечто такое:

class Object_A
{
public:
    static Object_Ptr construct(const std::string&, uint16_t);
};

typedef boost::function<Object_Ptr (const std::string&, uint16_t)> Constructor;

typedef std::vector<Constructor> Constructor_Array;

Constructor_Array constructors;

void init_constructors()
{
#ifdef WITH_PROTOCOL_A
    constructors.push_back(boost::bind(&Object_A::construct, _1, _2));
#endif// WITH_PROTOCOL_A
#ifdef WITH_PROTOCOL_B
    constructors.push_back(boost::bind(&Object_B::construct, _1, _2));
#endif// WITH_PROTOCOL_B
}

Object_Ptr create_object(const std::string& host, uint16_t port)
{
    for (Constructor_Array::iterator iter = constructors.begin();
         iter != constructors.end(); ++iter)
    {
        try {
            return (*iter)(host, port);
        } catch (const Protocol_Exception&) {
            // Wrong protocol
        }
    }

    // No suitable protocol has been found
    return Object_Ptr();
}

undet
() автор топика
Ответ на: комментарий от namezys

> создавай фабрики для каждого протокола, зарегестрируй их, а уж потом в цикле перебирай фабрики

если протокол выбирается на этапе конпеляции — фабрика не нужна.

name_no ★★
()

Что-то попахивает ересью. Покажи код, который вызывает create_object.

runtime ★★★★
()
Ответ на: комментарий от undet

> Это выглядит чуть чише и проще, но не слишком сильно. Или про что ты говорил?

Да, что-то вроде этого я и имел в виду. Тут уже предложили фабрику наваять — можно и так попытаться.

one_more_hokum ★★★
()

>Есть ли какие-то общепринятые методы, идиомы и т.д., чтобы покрасивее такое сделать?

Шаблоны

annulen ★★★★★
()
Ответ на: комментарий от one_more_hokum

навояй. на шаблонох просто будет. и у тебя только в одном месте будет разбор defin'ов, а потом данамически. или статически-типизированно-шаблонизированно

namezys ★★★★
()
Ответ на: комментарий от name_no

> протокол выбирается на этапе конпеляции

Не совсем так. Для определённости: есть куча разных видео серверов, каждый из которых работает по своему протоколу. Для каждого из них я знаю только хост и номер порта. Протокол предстоит определить во время исполнения. Библиотека предоставляет единый интерфейс для разных видео серверов, и может быть собрана с поддержкой разных протоколов, в зависимости от того, какие из них нужны и будут использоваться. Пользователь библиотеки делает такое: [code] Video_Server_Ptr video_server = create_video_server(host, port); [/code] Вся магия происходит внутри create_video_server, и пользователь работает с видео сервером через этот объект не заботясь о протоколах и т.п.

undet
() автор топика
Ответ на: комментарий от undet

> Протокол предстоит определить во время исполнения. Библиотека предоставляет единый интерфейс для разных видео серверов, и может быть собрана с поддержкой разных протоколов, в зависимости от того, какие из них нужны и будут использоваться.

С этого надо было начинать. В таком случае намного красивее уже предложенных вариантов не сделаешь.

runtime ★★★★
()
struct object_a_creator{
    static object_ptr create(std::string const& host, uint16_t port);
};

struct object_b_creator{
    static object_ptr create(std::string const& host, uint16_t port);
};


template<class creator_type>
struct object_creator_impl:creator_type
{
    object_ptr create_object(std::string const& host, uint16_t port)
    {
        try
        {
            return creator_type::create(host,port);
        }
        catch(protocol_exception const&)
        {
            ;
        }
    }

#ifdef WITH_PROTOCOL_A
    typedef object_creator_impl<object_a_creator> object_creator;
#else
    typedef object_creator_impl<object_b_creator> object_creator;
#endif

object_ptr ptr = object_creator::create_object("",0);
kitov ★★★
()
Ответ на: комментарий от undet

Кстати, нет ли в стандартной бибилиотеке или в boost'е подобного шаблона:

template <class Type,
          class Result_Type,
          class Argument_1_Type,
          class Argument_2_Type,
          class Argument_3_Type>
struct Generic_Creator
{
    Result_Type*
    operator()(const Argument_1_Type& arg_1,
               const Argument_2_Type& arg_2,
               const Argument_3_Type& arg_3) const
    {
        return new Type(arg_1, arg_2, arg_3);
    }
};

undet
() автор топика

http://www.drdobbs.com/blog/archives/2010/06/the_x_macro.html;jsessionid=XFHI...

идиома

#define COLORS \
    X(red) \
    X(blue) \
    X(green)

#define X(a) C##a,
enum Color { COLORS };
#undef X

#define X(a) #a,
static char *ColorStrings[] = { COLORS };
#undef X

твой пример можно переписать так:


#define WITH_PROTO(NAME) \
#ifdef WITH_PROTOCOL_##NAME \
    try {  \
        return Object_Ptr(new Object_##NAME(host, port)); \
    } catch (const Protocol_Exception&) { \
        // nil \
    } \
#endif// WITH_PROTOCOL_##NAME

#define WITH_ALL_PROTOCOLS \
         X(A) \
         X(B) 


Object_Ptr create_object(const std::string& host, uint16_t port) 
{ 
#define X(a) WITH_PROTO(a)
WITH_ALL_PROTOCOLS
#undef X
}

Если внутри блока WITH_PROTO(х) понадобится «имя» х, используй #NAME вместо PREFIX##NAME

anonymous
()
Ответ на: комментарий от undet

наверно, где-то есть, если поискать:

Of course, you may already be using a macro or variable named X, and X is hardcoded in the macro body. Andrei Alexandrescu suggests the following improvement where the X macro is itself a parameter:

#define FOR_ALL_COLORS(apply) \
apply(red) \
apply(blue) \
apply(green)
And later:

#define SELECT_STRING(a) #a,
static char *ColorStrings[] =
{ FOR_ALL_COLORS(SELECT_STRING) };
#undef SELECT_STRING

можно что-то накрутить с итераторами Буста

anonymous
()
Ответ на: комментарий от undet

итого: constructors ты заполняешь на этапе конпеляции, а цикл идёт во время запуска программы. Этот цикл можно заменить на бустовый for_each, чтобы цикл развернулся во время конпеляции тоже.

anonymous
()

А я бы сделал как-то так, без шаблонов:

#include <string>

class Object {};
class Object_A:public Object {};
class Object_B:public Object {};
class Object_C:public Object {};
typedef Object* Object_Ptr;

enum {ProtocolA=0,ProtocolB=1,ProtocolC=2};

inline Object_Ptr create_object(const std::string& host, uint16_t port,int protocol=ProtocolA) 
{ 
  try
  {
    if(protocol==ProtocolA)
    {
      return new Object_A();
    }
    else if(protocol==ProtocolB)
    {
      return new Object_B();
    }
    else if(protocol==ProtocolC)
    {
      return new Object_C();
    }
  }
  catch(int e)
  { 
        // nil 
  }
  return Object_Ptr(); 
} 


int main()
{
  Object_Ptr ptr = create_object("bla-bla-bla",666);
  return 0;
}
Так-как функция inline и переменная protocol известна на этапе компиляции, то компилятор уберет лишнее. Шаблоны нужны если тип возвращаемой переменной меняется.

pathfinder ★★★★
()
Ответ на: комментарий от namezys

>ага. и 100 строк кода if/return/else

Зато просто и интуитивно понятно. Хотя согласен. Для сотни вариантов лучше шаблоны.

pathfinder ★★★★
()
Ответ на: комментарий от pathfinder

>Так-как функция inline и переменная protocol известна на этапе компиляции, то компилятор уберет лишнее.

я бы на это не рассчитывал. функция большая, и «умный» GCC вполне может ее разинлайнить. Тогда уж сделать шаблонную функцию от int

annulen ★★★★★
()
Ответ на: комментарий от pathfinder

я имел в виду create_object<int>(const std::string& host, uint16_t port)

annulen ★★★★★
()
Ответ на: комментарий от namezys

>Понятнее для непрофи - это да

Это нечестный прием.

Шаблоны кстати тоже не панацея. Это компромисс между DSL и прямым написанием кода без фокусов с метапрограммированием.

Можно ведь сделать полноценный DSL. Тогда это совсем Ъ подход. Все будет предельно лаконично. Но этот подход потянет за собой другие проблемы.

pathfinder ★★★★
()
Ответ на: комментарий от namezys

>DSL на С++ --- вы извращенец

Вы меня не поняли. Я не агитирую за DSL.

З.Ы. Сказанное можно понять, будто С++ избавляет от любой необходимости использовать DSL. Как я уже сказал в нем есть компромиссные решения, но не более того.

pathfinder ★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.