Версия для печати темы
Нажмите сюда для просмотра этой темы в оригинальном формате
Форум программистов > C/C++: Общие вопросы > variadic parameters to base class constructor


Автор: InvalidProperty 29.1.2010, 17:05
Передо мной возникла проблема, решение которой я считаю достойным публикации, дабы остальное сообщество могло бы найти решение поставленной проблемы по возможности быстрее, чем это удалось мне.

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

Идея:
Имеется базовый класс, конструктор которого принимает переменное число аргументов
Код

class A {
 public:
  A(const char *format, ...);
};

Имеется дочерний класс. Его конструктор также должен принимать переменное число аргументов и создавать базовый класс.

Постановка вопроса:
Как передать переменное число аргументов из конструктора дочернего класса в конструктор базового?

Размышления:
Чтобы произвести создание базового класса из конструктора дочернего с указанием некоторых параметров для конструктора базового класса, нужно написать примерно следующее:
Код

class A {
 public:
  A(int i);
};

class B : public A {
 public:
  B(int i) : A(i);          // создание базового класса с параметром
}

Но такая махинация для переменного числа параметров потерпит фиаско.

Решение:
В базовом классе создается статическая функция, которая будет отвечать за упаковку переменных параметров в строку:
Код

class A {
 private:
  char _str[MAXLINE];

 public:
  static char *pack(const char *format, ...);
  A(const char *str)
  {
   strcpy(_str, str);        // необходимо для того, чтобы не
   delete[] str;              // возникало утечек памяти
   printf("%s", _str);
  }
};

class B : public A {
 public:
  B(const char *str) : A(str) {}
};

int main()
{
 B b(A::pack("ok, it works fine %d times", 454));

 return 0;
}

и это будет работать!. Но! Как только мы попытаемся вызвать конструктор либо базового, либо дочернего класса, передавая строку, лежащую на стеке, или в статической памяти, приложение вылетит:
Код

B b("asfasdf");       // возникнет исключение, т.к. в конструкторе базового класса строка уничтожается

Для того, чтобы решить проблему, нужно создать некую структуру, которая будет содержать в себе строку, передаваемую конструкторам. Определение этой структуры ДОЛЖНО находиться в базовом классе, чтобы избежать создания такой структуры напрямую.

Полный код решения:
Код

#include <stdio.h>
#include <stdarg.h>
#include <string.h>


class A
{
public:
 char _str[453];
public:
 struct SS
 {
  char str[435];
 };

 static SS *pack(const char *format, ...)
 {
  SS        *sss = new SS;
  va_list    ap;

  va_start(ap, format);
  vsnprintf(sss->str, sizeof(sss->str), format, ap);
  va_end(ap);

  return sss;
 }

 A(SS *sss)
 {
  strcpy(_str, sss->str);
  delete sss;
  printf("constructor A\n");
  printf("A str = [%s]\n", _str);
 }
};

class B : public A
{
 public:
  B(SS *sss) : A(sss)
  {
   printf("constructor B\n");
   printf("B str = [%s]\n", _str);
  }
};

int main()
{
 B b(A::pack("asdfa %d", 5));
 SS s;                                            // эта строка вызовет ошибку компиляции, если ее убрать, программа будет работать отлично

 printf("%s\n", b._str);

 return 0;
}

Надеюсь, пояснений к коду не потребуется, но готов ответить на все вопросы smile

Автор: InvalidProperty 29.1.2010, 17:40
т.о. мы можем написать что-то вроде
Код

throw MyDerivedException(MyBaseException::pack("exception N %d", 45));

и получим именно то, что хотим

Автор: mes 29.1.2010, 17:49
Цитата(InvalidProperty @  29.1.2010,  16:05 Найти цитируемый пост)
Надеюсь, пояснений к коду не потребуется, но готов ответить на все вопросы smile

А теперь ( без решения и внутренних составляющих) , что и из чего Вы хотите получить ?
а то разглядывать код времени нет, а сути вопроса снаружи не видно..

Автор: zim22 29.1.2010, 17:54
Цитата(InvalidProperty @  29.1.2010,  16:05 Найти цитируемый пост)
Как передать переменное число аргументов из конструктора дочернего класса в конструктор базового?

вот так
Код

struct S {
  S(int x, ...) {}
};

struct Z : S {
  Z() : S(10, "yo", 22.33) { }
};

Автор: InvalidProperty 29.1.2010, 17:56
zim22, задача заключалась в передачи из конструктора дочернего класса именно тех же переменных параметров, что были переданы в него, а не просто каких-то

Добавлено через 59 секунд
mes, вторым постом я привел строку, которую можно использовать, применив данный метод

Добавлено через 2 минуты и 11 секунд
zim22, тем более, молодой человек, судя по коду, который Вы привели, Вы не прочитали пост вообще

Автор: zim22 29.1.2010, 18:08
Цитата(InvalidProperty @  29.1.2010,  16:56 Найти цитируемый пост)
 тем более, молодой человек, судя по коду, который Вы привели, Вы не прочитали пост вообще

я хоть свой возраст указал в инфо. 
насколько же вы молодой или старый - могу лишь догадываться.

но в одном я уверен на 100% - есть более изящный метод решения вашей теоретической проблемы.
***
и вообще, причём здесь возраст? smile 
"одни с годами умнеют - другие просто становятся старше."

Автор: InvalidProperty 29.1.2010, 18:10
Цитата(zim22 @  29.1.2010,  18:08 Найти цитируемый пост)
но в одном я уверен на 100% - есть более изящный метод решения вашей теоретической проблемы. 

так а я ведь всегда за здоровую критику. Давайте обсудим Ваш метод

Автор: zim22 29.1.2010, 18:27
InvalidProperty, ок. напиши проблему, решение которой ты видишь только через использование ellipsis'a. 
я попробую решить её без них.

Автор: InvalidProperty 29.1.2010, 18:58
zim22, да что далеко ходить? Мне нужен класс исключений, который наследован от некоторого базового класса исключений. Нужно, чтобы и дочерний и базовый класс поддерживали конструкцию выбрасывания исключений с переменным числом параметров. Как Вы видите ее решение?

Код

throw DerivedException("some i parameter = %d, some j = %d, some string = %s", i, j, str);

Автор: zim22 29.1.2010, 19:02
Цитата(InvalidProperty @  29.1.2010,  17:58 Найти цитируемый пост)
. Нужно, чтобы и дочерний и базовый класс поддерживали конструкцию выбрасывания исключений с переменным числом параметров

это не проблема. это деталь реализации.
***
примеры проблем:
1) есть картотека. для каждой из записей картотеки нужно применять разные алгоритмы анализа в зависимости от типа записи.
2) ...

Автор: InvalidProperty 29.1.2010, 19:12
zim22, Вы к чему это сказали? Вы вообще топик этот прочитали или просто пустились в философию? Топик посвящен конкретной теме. Если есть иное решение, то покажите, и мы его обсудим, а иначе получается, что Вы просто льете воду. Зачем топик засорять?

Автор: Леопольд 29.1.2010, 19:32
erased

И вообще, элипсис это рудимент доставшийся от С... smile

Автор: NewDima 29.1.2010, 19:40
а я извращенец  smile , воспринимайте, пожалуйста как шутку, ибо я решил просто сделать для семи параметров...
Код

#define PVOID(a) (reinterpret_cast<void*>(a))
#define DEFAULT_PARAMETERS_TYPES void* i1 = 0, void* i2 = 0, void* i3 = 0, void* i4 = 0, void* i5 = 0, void* i6 = 0, void* i7 = 0
#define DEFAULT_PARAMETERS_LIST i1, i2, i3, i4, i5, i6, i7
class Base_Exception {
protected:
    Base_Exception(char* format, DEFAULT_PARAMETERS_TYPES) {
        int k, a = 0;
        _asm {
            pusha
        }
        for (void **v = &i7; v >= &i1; v--) {
            if (!*v) continue;
            ++a;
            k = reinterpret_cast<int>(*v);
            _asm {
                push k;
            }
        }
        printf(format);
        while (a--) _asm {pop eax}
        _asm {
            popa
        }
    }
};
class Derived_Exception: public Base_Exception {
public:
    Derived_Exception(char* format, DEFAULT_PARAMETERS_TYPES):Base_Exception(format, DEFAULT_PARAMETERS_LIST) {
    }
};
...
Derived_Exception ex("%d %d %s", PVOID(1), PVOID(2), "ahhaha");

 smile 

Автор: zim22 29.1.2010, 19:48
Цитата(InvalidProperty @  29.1.2010,  18:12 Найти цитируемый пост)
Вы к чему это сказали?

проведу аналогию. уж люблю я их smile
Цитата

один молодой человек решил поехать за рубеж. взял он с собой кусок сала и 3 булки хлеба.
на таможне его остановили и велели предъявить паспорт: а он им и говорит:
паспорта у меня нету. он мне и не нужен. у меня есть кусок сала. хотите поделюсь?

мораль сей басни такова: не нужно решать задачу неправильными методами.
чтобы узнать какой метод правильный - нужно сначала понять саму задачу.

Автор: andrew_121 29.1.2010, 20:05
Цитата(zim22 @ 29.1.2010,  19:48)
Цитата(InvalidProperty @  29.1.2010,  18:12 Найти цитируемый пост)
Вы к чему это сказали?

проведу аналогию. уж люблю я их smile
Цитата

один молодой человек решил поехать за рубеж. взял он с собой кусок сала и 3 булки хлеба.
на таможне его остановили и велели предъявить паспорт: а он им и говорит:
паспорта у меня нету. он мне и не нужен. у меня есть кусок сала. хотите поделюсь?

мораль сей басни такова: не нужно решать задачу неправильными методами.
чтобы узнать какой метод правильный - нужно сначала понять саму задачу.

философ епс smile 

ну расскажи что думаешь об этом. меня тоже такая задача посещает время от времени.

Автор: baldina 29.1.2010, 20:24
InvalidProperty, при помощи синтаксиса С++ данную проблему изящно не решить. ковыряясь только в конструкторах это невозможно.
Однако возник вопрос: что делает конструктор базового класса с переменным числом параметров, тип которых не проверяется в процессе компиляции? вероятно скармливает тому же vsnprintf. Тогда следующий вопрос - зачем классу исключений вообще этим заниматься? пусть конструирует строку тот, кто генерирует исключение, а класс исключения работает с постоянным числом параметров.  Всё, проблемы нет.
Обратите внимание: ваш pack() - совершенно инородный элемент в классе, класс нагружен не свойственным ему функционалом. Разделите функционал и проблема исчезнет.

Автор: zim22 29.1.2010, 20:26
Цитата(andrew_121 @  29.1.2010,  19:05 Найти цитируемый пост)
ну расскажи что думаешь об этом. меня тоже такая задача посещает время от времени.

вот самый простой вариант. чем не устраивает?
Код

#include "stdafx.h"
#include <iostream>

struct BaseExceptionClass {
  BaseExceptionClass(const char *message) : message_(message) { }
  const char *what() { return message_; }
private:
  const char *message_;
};

struct DerivedExceptionClass : BaseExceptionClass {
  DerivedExceptionClass(const char *message) 
    : BaseExceptionClass(message) { }
};

int _tmain(int argc, _TCHAR* argv[])
{
  const char *text = "superb";
  int number = 88;

  try {
    char buffer[256] = {0};
    sprintf(buffer, "hello, i am %s, %d", text, number);

    throw DerivedExceptionClass(buffer);

  } catch (DerivedExceptionClass &ex) {
    std::cout << ex.what();
  }
  return 0;
}


Автор: baldina 29.1.2010, 20:29
в бусте кстати есть всякие форматтеры, которые позволяют конструировать на лету строки, имеющие сложный формат. это я к тому, что проблема форматирования строк не нова и давно решена, а класс исключения видимо будет ругаться в окошках. чем? правильно, строками.

предваряя возможную дискуссию о разном числе неких разнотипных свойств, которые надо обрабатывать классу исключения, предложу в этом случае воспользоваться
std::vector <boost::variant> или std::vector <boost::any>.

Добавлено через 3 минуты и 7 секунд
Цитата(zim22 @  29.1.2010,  19:02 Найти цитируемый пост)
Цитата(InvalidProperty @  29.1.2010,  17:58 )
. Нужно, чтобы и дочерний и базовый класс поддерживали конструкцию выбрасывания исключений с переменным числом параметров

это не проблема. это деталь реализации.

 smile 

Автор: andrew_121 29.1.2010, 20:38
Цитата(baldina @  29.1.2010,  20:24 Найти цитируемый пост)
Однако возник вопрос: что делает конструктор базового класса с переменным числом параметров, тип которых не проверяется в процессе компиляции? вероятно скармливает тому же vsnprintf. Тогда следующий вопрос - зачем классу исключений вообще этим заниматься? пусть конструирует строку тот, кто генерирует исключение, а класс исключения работает с постоянным числом 

это верно. но наверняка не только мне и InvalidProperty хотелось бы получить такую возможность. и желательно не только строковое сообщение. было бы супер!
пример:
Код

int main() {
   try {
      std::ifstream file(name);
      if ( !file ) {
         throw exception_type("fname %s, error code%d", name, errno);
      }
   } catch(const exception_type& e) {
      std::cout << e.what() << std::endl;
      std::cout << e.func() << std::endl;
      std::cout << e.line() << std::endl;
      std::cout << e.ecode() << std::endl;
   }
   return 0;
}

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

Автор: baldina 29.1.2010, 20:41
andrew_121, 

Цитата(baldina @  29.1.2010,  20:29 Найти цитируемый пост)
предваряя возможную дискуссию о разном числе неких разнотипных свойств, которые надо обрабатывать классу исключения, предложу в этом случае воспользоваться
std::vector <boost::variant> или std::vector <boost::any>.


Автор: andrew_121 29.1.2010, 20:45
baldina, по моему раньше этого не было. во всяком случае я этого не заметил.

еще думаю для этого подойдет boost.format

Автор: baldina 29.1.2010, 20:52
andrew_121, кажется то, что выше, вы не читаете  smile 

Автор: andrew_121 29.1.2010, 21:21
Цитата(zim22 @  29.1.2010,  20:26 Найти цитируемый пост)
вот самый простой вариант. чем не устраивает?

этим -
Цитата(zim22 @  29.1.2010,  20:26 Найти цитируемый пост)
sprintf(buffer, "hello, i am %s, %d", text, number);


Автор: Lazin 29.1.2010, 21:40
как-же мы любим придумывать несуществующие проблемы и решать их smile 
я, в свою очередь, хочу предложить такой способ решения проблемы передачи переменного числа параметров в конструктор базового класса
file: generate_ctor.ipp
Код

Derived(PARAMETER_DEFINITION_LIST) : Base(PARAMETER_LIST)
{
}
#undef PARAMETER_DEFINITION_LIST
#undef PARAMETER_LIST

other file:
Код

struct Base
{
    Base(int a) { std::cout << "1" << std::endl; }
    Base(int a, int b) { std::cout << "2" << std::endl; }
};

struct Derived : public Base
{
#define PARAMETER_DEFINITION_LIST int a
#define PARAMETER_LIST a
#include "generate_ctor.ipp"

#define PARAMETER_DEFINITION_LIST int a, int b
#define PARAMETER_LIST a, b
#include "generate_ctor.ipp"
};


Цитата(andrew_121 @  29.1.2010,  20:38 Найти цитируемый пост)
это верно. но наверняка не только мне и InvalidProperty хотелось бы получить такую возможность. и желательно не только строковое сообщение. было бы супер!

тогда тебе как-то придется генерировать класс с переменным числом членов и недетерминированым набором методов  smile 

Автор: Lazin 29.1.2010, 21:58
Цитата(InvalidProperty @  29.1.2010,  17:40 Найти цитируемый пост)
Код

throw MyDerivedException(MyBaseException::pack("exception N %d", 45));

я не вижу смысла в этом, класс исключения - Copy Constructable, поэтому можно писать так:
Код

throw MyDerivedException(MyBaseException("exception N %d", 45));


 smile 

Автор: mes 29.1.2010, 22:33
Цитата(InvalidProperty @  29.1.2010,  16:56 Найти цитируемый пост)
mes, вторым постом я привел строку, которую можно использовать, применив данный метод

тогда Вы неправильно поняли мое непонимание. Меня интересовала именно задача, а не неполучающийся способ ее реализовать.
Ну вообщем тут уже много ответили, но еще раз проакцентирую внимание:
Цитата(zim22 @  29.1.2010,  18:48 Найти цитируемый пост)
не нужно решать задачу неправильными методами.
чтобы узнать какой метод правильный - нужно сначала понять саму задачу. 

 smile 

Автор: InvalidProperty 30.1.2010, 16:58
меня навели на мысль еще одного варианта решения, который, как мне кажется, является более изящным, чем первый:
Код

#include <stdio.h>
#include <stdarg.h>
#include <string.h>

#define MAXLINE        4096


class exFmt
{
 private:
  char _str[MAXLINE];

 public:
  const char *str()
   { return _str; }

  exFmt(const char *format, ...)
  {
   va_list    ap;
   va_start(ap, format);
   vsnprintf(_str, sizeof(_str), format, ap);
   va_end(ap);
  }

  exFmt(const exFmt &fmt)
   { memcpy(_str, fmt._str, sizeof(_str)); }

  exFmt() { }
};

class exBase
{
 private:
  exFmt        _fmt;

 public:
  const char *str()
   { return _fmt.str(); }

  exBase(const exFmt &fmt)
   { _fmt = fmt; }
};


class exDerived : public exBase
{
 public:
  exDerived(const exFmt &fmt) : exBase(fmt) { }
};


int main()
{
 try
 {
  throw exDerived(exFmt("the error occured in %d try-catch block", 1));
 }
 catch(exBase &ex)
 {
  printf("%s\n", ex.str());
 }

 return 0;
}

Автор: Lazin 30.1.2010, 18:17
этот вариант ни разу не изящный, обычный std::exception + boost.format - намного удобней, а главное обходятся без элипсиса smile 

Автор: InvalidProperty 30.1.2010, 19:21
Lazin, удобство понятие относительное и сугубо индивидуальное. Не нужно излишних холиваров. Я, к примеру, недолюбливаю сторонние библиотеки, а строка форматирования printf для меня выглядит привычнее и удобнее.

Автор: Lazin 30.1.2010, 20:00
Цитата(InvalidProperty @  30.1.2010,  19:21 Найти цитируемый пост)
Lazin, удобство понятие относительное и сугубо индивидуальное
согласен, но ведь еще существуют объективные признаки кода с "душком", в данном случае это оно и есть smile 
я лично не понимаю, зачем все это, если можно просто отформатировать строку и передать ее в конструктор класса исключения, еще я не понимаю, почему не используется std::exception, а используется свой велосипед
ну и на последок, если я захочу получить тот или иной параметр не в текстовом виде, что мне делать?

Цитата(InvalidProperty @  30.1.2010,  19:21 Найти цитируемый пост)
Я, к примеру, недолюбливаю сторонние библиотеки, а строка форматирования printf для меня выглядит привычнее и удобнее.

лично я бы не хотел работать в одной команде с вело-строителем любителем, потому-что вместо того, что-бы просто писать код, придется изучать аналоги уже хорошо известных вещей, вроде boost::format и std::exception
вот к примеру вариант с boost::exception
(не ручаюсь за правильность, пишу по памяти)
Код

typedef boost::error_info<struct tag_line_number, std::string> LineNumberErrInfo;
typedef boost::error_info<struct tag_file_name, std::string> FileNameErrInfo;

class SpecificError : public boost::exception, public std::exception
{
};

....

try
{
...
   throw SpecificError() << LineNumberErrInfo(_LINE_) << FileNameErrInfo(__FILE__);
}
catch(SpecificError& e)
{
    std::cout << boost::diagnostics_information(e) << std::endl;
    std::cout << "Line: " << boost::get_error_info<LineNumberErrInfo>(e) << std::endl;
    ...
}

по крайней мере это будет понятно любому нормальному С++ программисту, а еще здесь можно получить любой из параметров или добавить туда новые параметры и заново бросить это исключение, можно передать его в другой поток

Автор: andrew_121 31.1.2010, 18:14
мдя...
http://evgeny-lazin.blogspot.com/2008/11/boostsystem.html

Автор: BEOWOLF 1.2.2010, 02:15
Цитата

Но! Как только мы попытаемся вызвать конструктор либо базового, либо дочернего класса, передавая строку, лежащую на стеке, или в статической памяти, приложение вылетит:
а почему вдруг приложение должно вылететь? Ну если строка в стеке удалена к моменту выполнения конструктора - то конечно вылетит, ну а в статической памяти - только если её вручную удалить.

А вообще подобного рода проблема решается другим, изящным способом - через использование объектов, содержащих свой конструктор/деструктор. То есть, вместо передачи const char* в качестве параметра, лучше передать const std::string&, в этом случае код всегда адекватно выполнится. Ну а если нужно передать форматированную строку, то пишите вот такую вот функцию:
Код

inline std::string __cdecl format(LPCSTR lpszFormat, ...)
{
    va_list argList;
    va_start(argList, lpszFormat);

    size_t bsz = _vscprintf(lpszFormat, argList);
    std::string res((std::string::size_type)bsz, 0);
    vsprintf_s((LPSTR)res.data(), bsz+1, lpszFormat, argList);

    return res;
};
и дальше в конструкторе можно написать вызов типа A(format("1param = %u, 2param = %u", i, j));
Кроме того, незабываем использовать auto_ptr, если всё-таки есть работа с выделенными объектами и выброс исключений, а ещё лучше умные указатели smile

Автор: xvr 1.2.2010, 15:18
В таком случае я обычно делаю немного по другому:
Код

class BaseEx {
public:
 BaseEx();

 void init(const char*, va_list);
};

class DerivedEx : public BaseEx {
public:
 DerivedEx(const char* msg, ...)
  {
   va_list p;
   va_start(msg,p);
   init(msg,p);
   va_end(p);
  }
};



Powered by Invision Power Board (http://www.invisionboard.com)
© Invision Power Services (http://www.invisionpower.com)