C ++: автоматическая инициализация - PullRequest
3 голосов
/ 22 сентября 2010

Меня иногда раздражает необходимость инициализировать все POD-типы вручную. Э.Г.

struct A {
    int x;
    /* other stuff ... */

    A() : x(0) /*...*/ {}
    A(/*..*/) : x(0) /*...*/ {}
};

Мне это не нравится по нескольким причинам:

  • Я должен повторить это в каждом конструкторе.
  • Начальное значение находится в другом месте, чем объявление переменной.
  • Иногда единственная причина, по которой я должен реализовать конструктор, заключается в этом.

Чтобы преодолеть это, я стараюсь вместо этого использовать свои собственные типы. То есть вместо использования int x,y; я использую собственную векторную структуру, которая также автоматически инициализируется с помощью 0. Я также подумал о том, чтобы просто реализовать несколько простых типов-оболочек, таких как:

template<typename T>
struct Num {
    T num;
    Num() : num(0) {}
    operator T&() { return num; }
    operator const T&() const { return num; }
    T& operator=(T _n) { num = _n; return num; }
    /* and all the other operators ... */
};

Это в основном решает эту проблему до сих пор для всех случаев, когда я хочу начать с 0 (это, безусловно, самые частые случаи для меня).

Спасибо Джеймсу Макнеллису за подсказку: это также можно решить с помощью boost::value_initialized.


Теперь, не ограничиваясь POD-типами:

Но иногда я хочу инициализировать чем-то другим, и снова возникают проблемы, потому что шаблонная структура Num не может быть легко расширена, чтобы допустить это. В основном потому, что я не могу передать числа с плавающей запятой (например, float) в качестве параметра шаблона.

На Java я бы просто сделал:

class A {
    int x = 42;
    /*...*/

    public A() {}
    public A(/*...*/) { /*...*/ }
    public A(/*...*/) { /*...*/ }
    /*...*/
}

Мне очень важно, что в тех случаях, когда вы хотите всегда инициировать переменную-член одинаково во всех возможных конструкторах, вы можете записать значение init непосредственно рядом с переменной-членом, как в int x = 42;.

То, что я пытался решить, это сделать то же самое в C ++.

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

#define _LINENAME_CAT( name, line ) name##line
#define _LINENAME( name, line ) _LINENAME_CAT( name, line )

/* HACK: use _LINENAME, workaround for a buggy MSVC compiler (http://connect.microsoft.com/VisualStudio/feedback/ViewFeedback.aspx?FeedbackID=360628)*/
#define PIVar(T, def) \
struct _LINENAME(__predef, __LINE__) { \
 typedef T type; \
 template<typename _T> \
 struct Data { \
  _T var; \
  Data() : var(def) {} \
 }; \
 Data<T> data; \
 T& operator=(const T& d) { return data.var = d; } \
 operator const T&() const { return data.var; } \
 operator T&() { return data.var; } \
}

(Для других компиляторов я могу просто опустить это _LINENAME имя для структуры и просто оставить его без имени. Но MSVC это не нравится.)

Теперь это работает более или менее так, как я хочу. Теперь это будет выглядеть так:

struct A {
    PIVar(int,42) x;
    /*...*/

    A() {}
    A(/*...*/) { /*...*/ }
    A(/*...*/) { /*...*/ }
    /*...*/
};

Хотя он делает то, что я хочу (в основном), я все еще не полностью доволен этим:

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

Как вы решили это? Есть лучшее решение?


Был ответ, который был снова удален, в котором говорилось, что C ++ 0x допускает в основном тот же синтаксис, что и в Java. Это правда? Тогда мне просто нужно ждать C ++ 0x.


Пожалуйста, не давайте никаких комментариев, таких как:

  • "тогда просто используйте Java" / "не используйте C ++ тогда" или
  • «если вам нужно что-то подобное, вы, вероятно, делаете что-то не так» или
  • "просто не делай так".

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

Ответы [ 6 ]

10 голосов
/ 22 сентября 2010

Иногда единственная причина, по которой я должен реализовать конструктор, - это.

Тебе не обязательно это делать.

struct POD {
  int i;
  char ch;
};

POD uninitialized;

POD initialized = POD();

В равной степени в списке инициализации:

class myclass
  POD pod_;
  // ....
  myclass()
   : pod_() // pod_'s members will be initialized
  {
  }

Чтобы преодолеть это, я вместо этого стараюсь использовать свои собственные типы.

Ваш тип не срабатывает в этом сценарии:

void f(int&);

Num<int> i;
f(i);

Вероятно, есть еще проблемы, но это то, что пришло мне в голову немедленно.


Как вы решили это? Есть лучшее решение?

Да, мы все решили это. Мы сделали не пытаясь бороться с языком, а использовать его так, как он был создан: инициализировать POD в списках инициализации. Когда я вижу это:

struct ML_LieroX : MapLoad {        
    std::string       id;
    PIVar(int, 0)     type;
    std::string       themeName;
    PIVar(int, 0)     numObj;
    PIVar(bool,false) isCTF;

Я съеживаюсь. Что это делает? Почему так? Это даже C ++?

Все это только для того, чтобы сохранить несколько нажатий клавиш, набрав список инициализации? Ты вообще серьезно?

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

7 голосов
/ 22 сентября 2010

Boost предоставляет шаблон value_initialized<T>, который можно использовать для гарантии инициализации объекта (POD или нет). Его документация подробно описывает все плюсы и минусы его использования.

Ваша жалоба на неспособность автоматически инициализировать объект заданным значением не имеет большого смысла;это не имеет ничего общего с объектом, являющимся POD;если вы хотите инициализировать тип не POD со значением не по умолчанию, вы должны указать это значение при инициализации.

2 голосов
/ 22 сентября 2010

Вы можете инициализировать структуры POD следующим образом:

struct POD
{
    int x;
    float y;
};

int main()
{
    POD a = {};            // initialized with zeroes
    POD b = { 1, 5.0f };   // x = 1, y = 5.0f

    return 0;
}
0 голосов
/ 17 декабря 2013

До C ++ 0x было решение, которое хорошо работает, если ненулевое значение, которое вы хотите инициализировать, не является полностью произвольным (что обычно имеет место на практике).Аналогично boost :: initialized_value, но с дополнительным аргументом для получения начального значения (которое становится немного суетливым из-за C ++).

template<typename T> struct Default { T operator()() { return T(); } };
template<typename T, T (*F)()> struct Call { T operator()() { return F(); } };
template<int N> struct Integer { int operator()() { return N; } };

template< typename X, typename Value = Default<X> >
class initialized {
public:
  initialized() : x(Value()()) {}
  initialized(const X& x_) : x(x_) {}
  const X& get() const { return x; }
  operator const X&() const { return x; }
  operator X&() { return x; }
private:
  X x;
};

Вы можете использовать его так:

struct Pi { double operator()() { return 3.14; } }; //Exactly
const char* init_message() { return "initial message"; }
Point top_middle() { return Point(screen_width()/2, 0); }

struct X {
  initialized<int> a;
  initialized<int, Integer<42> > b;
  initialized<double> c;
  initialized<double, Pi> d;
  initialized<std::string> e;
  initialized<std::string, Call<const char*, init_message> > f;
  initialized<Point> g;
  initialized<Point, Call<Point,top_middle> > h;
  X() {}
};

Я нахожу раздражение в связи с необходимостью создания фиктивной функции для возврата любого нецелого / нестандартного значения, как правило, амортизируется по всей библиотеке (поскольку ненулевые начальные значения для определенного типа обычно используются многими классами).

Очевидно, что typedef здесь друг.

В любом случае, не могу дождаться обновления до C ++ 0x / 11/14 / что угодно.

0 голосов
/ 23 сентября 2010

C ++ имеет делегирование конструктора, так почему бы не использовать его?

struct AState
{
    int x;
    AState() : x(42) {}
};

class A : AState
{
    A() {}
    A(/*...*/) { /*...*/ }
    A(/*...*/) { /*...*/ }
};

Теперь инициализация x делегируется всеми конструкторами.Базовый конструктор может даже принимать аргументы, передаваемые из каждой версии A::A.

0 голосов
/ 22 сентября 2010

Существует предложение для C ++ 0x , которое позволяет это:

struct A {
    int x = 42;
};

Это именно то, что я хочу.

Если это предложение не делаетсяв окончательной версии возможность делегирования конструкторов - это еще один способ, по крайней мере, избежать перекодировки инициализации в каждом отдельном конструкторе (и в то же время избежать фиктивной вспомогательной функции для этого).*

В текущем C ++, кажется, нет лучшего способа сделать это, несмотря на то, что я уже продемонстрировал.

...