Есть ли способ поздней инициализации переменной-члена (класса) в С++?

Я исхожу из фона Java. У меня есть следующая программа.

#include <string>
#include <iostream>

class First {
    public:
    First(int someVal): a(someVal) {

    }
    int a;
};

class Second {
    public:
    First first;
    Second()  {   // The other option would be to add default value as ": first(0)"
        first = First(123);

    }
};

int main()
{
    Second second;
    std::cout << "hello" << second.first.a << std::endl;
}

В классе Second я хотел, чтобы переменная first оставалась неинициализированной, пока я специально не инициализирую ее в Second()'s constructor. Есть ли способ сделать это? Или у меня просто осталось 2 варианта?:

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

Я не могу инициализировать first в списке инициализаторов правильным значением, так как значение получается после какой-то операции. Таким образом, фактическое требуемое значение для first доступно только в конструкторе Second().


person linuxeasy    schedule 11.07.2014    source источник
comment
first будет инициализирован, как только вы создадите экземпляр Second. Нет никакого способа обойти это. Все, что вы можете сделать, это изменить его значение на более позднем этапе.   -  person juanchopanza    schedule 11.07.2014
comment
Используйте указатель. Я Java это все указатели, что вас смущает.   -  person Peter - Reinstate Monica    schedule 11.07.2014
comment
@juanchopanza, так что вы рекомендуете идти по этому пути: Second() : first(0) { first = First(123); }   -  person linuxeasy    schedule 11.07.2014
comment
Можно выполнить вычисления и использовать их для инициализации first в списке параметров. Используйте функцию.   -  person Mooing Duck    schedule 11.07.2014
comment
@PeterSchneider Верно, но я хочу, чтобы он использовал его как можно меньше. Чтобы не беспокоиться о деструкторе, операторе присваивания и конструкторе копирования   -  person linuxeasy    schedule 11.07.2014
comment
Я не вижу, что вы хотите сделать. Вы еще не хотите инициализировать first в конструкторе Second, вы все равно его инициализируете? Установите first в качестве указателя и установите его в NULL или nullptr для С++ 11 в вашем конструкторе. Затем на более позднем этапе используйте new First(arg) для создания нового указателя на объект типа First.   -  person PurityLake    schedule 11.07.2014
comment
Поэтому используйте соответствующий тип интеллектуального указателя, unique_ptr или shared_ptr, в зависимости от того, что должны делать d-tor, присваивание и копирование.   -  person Phil Miller    schedule 11.07.2014
comment
Здесь не место для указателя (умного или нет).   -  person James Kanze    schedule 11.07.2014


Ответы (6)


МОЕ предложение: используйте функцию:

private: static int calculate_first(int input) {return input*5;}
explicit Second(int input) : first(calculate_first(input)) {}

Базовые классы будут инициализированы в том порядке, в котором они объявлены в списке наследования классов, а затем члены будут инициализированы в том порядке, в котором они перечислены в классе, поэтому расчет может зависеть от не -статические переменные-члены и базовые классы, если они уже инициализированы.


Альтернативно:

Конструктор по умолчанию, затем переназначить:

explicit Second(int input) { first = input*5; }

Фиктивное значение, затем переназначьте:

explicit Second(int input) : first(0) { first = input*5; }

Используйте boost::Optional (или std::Optional в C++). 17):

boost::optional<First> first;
explicit Second(int input) { first = input*5; }

Используйте кучу:

std::unique_ptr<First> first;
explicit Second(int input) { first.reset(new First(input*5));}
Second(const Second& r) first(new First(*(r->first))) {}
Second& operator=(const Second& r) {first.reset(new First(*(r->first)));}

Новое размещение:

This is tricky and not suggested 
and worse in every way than boost::optional
So sample deliberately missing.
But it is an option.
person Mooing Duck    schedule 11.07.2014
comment
Что касается статической функции calculate_first, упомянутой в первом предложении, это не будет работать, если вычисления зависят от нестатических переменных-членов или переменных-членов базового класса, верно? - person linuxeasy; 11.07.2014
comment
@linuxeasy: базовые классы будут инициализированы в том порядке, в котором они объявлены в списке наследования классов, а затем члены будут инициализированы в том порядке, в котором они перечислены в классе, поэтому расчет может зависят от нестатических переменных-членов и базовых классов, если они уже инициализированы. - person Mooing Duck; 11.07.2014
comment
Практически всех, кроме первых двух, следует избегать. С другой стороны... Я иногда находил полезным предоставить упрощенный конструктор для чего-то вроде его First, который принимает явный, фиктивный параметр, чтобы сказать конструктору ничего не делать (или как можно меньше для последующего присваивания Работа). - person James Kanze; 11.07.2014
comment
К сожалению, сейчас C++20, и у нас до сих пор нет лучшего способа написать конструктор. - person RnMss; 31.01.2021
comment
@RnMss: И никогда не будет. Основная проблема заключается в том, как работает композиция. Это теоретические решения. Возможно, я что-то упускаю, но это никогда не станет лучше. Решение Java — это просто синтаксический сахар для варианта с кучей. - person Mooing Duck; 01.02.2021
comment
@MooingDuck Инициализация элементов, которые нельзя перемещать/копировать, будет катастрофой. - person RnMss; 02.02.2021
comment
И все же каким-то образом это работало для каждой программы на каждом языке до сих пор.... - person Mooing Duck; 02.02.2021

Инициализируйте first в списке инициализаторов участников.

Это может помочь выполнить ваши вычисления во вспомогательной функции и использовать конструктор пересылки:

class Second {
public:
    Second() : Second(helper_function()) {}

private:
    Second(int calc): first(calc) {}
    static int helper_function() { return ...; }

    First first;
};
person ecatmur    schedule 11.07.2014
comment
Что касается статической вспомогательной функции, упомянутой здесь, это не будет работать, если вычисления зависят от нестатических переменных-членов или переменных-членов базового класса, верно? - person linuxeasy; 11.07.2014
comment
@linuxeasy нет, не будет. Вы можете сделать его нестатической функцией-членом, если будете осторожны, ссылаясь только на члены, определенные до first. - person ecatmur; 11.07.2014

Это предложение является ядром проблемы:

Я не могу инициализировать первым в списке инициализаторов правильным значением, так как значение получается после какой-то операции.

Вы должны знать, что то, что вы хотите здесь сделать, не является идеальным стилем программирования на Java. Если оставить поле с некоторым значением по умолчанию, а затем присвоить его чуть позже после выполнения некоторых вычислений, это эффективно предотвращает его превращение в final и, следовательно, неизменность класса.

В любом случае ваша цель должна состоять в том, чтобы передать эти вычисления непосредственно в инициализацию члена, используя частные вспомогательные функции (которые могут быть статическими):

class Second {
private:
    First first;

    static int getInitializationData()
    {
        // complicated calculations go here...
        return result_of_calculations;
    }
public:
    Second() : first(getInitializationData()) {}
};

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

person Christian Hackl    schedule 11.07.2014

Вы можете просто делать то, что вы сказали в комментариях, или вы можете сначала сделать указатель на First и дать ему память, когда захотите, хотя я не рекомендую этот способ

person DTSCode    schedule 11.07.2014

Один из способов разделить время жизни объекта — использовать кучу, сделать first указателем и инициализировать его в любое время:

class Second {
public:
    First* first;
    Second()  { 
        first = new First(123);

    }
};

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

person Paul Evans    schedule 11.07.2014
comment
ну, я не думаю, что в этом контексте требуется интеллектуальный указатель, тем более что он находится в классе, он может просто удалить его в dtor() - person DTSCode; 11.07.2014
comment
@DTSCode a std::unique_ptr будет делать то же самое, что и delete в dtor, без необходимости писать дополнительный код в dtor. В чем преимущество не этого? - person Paul Evans; 11.07.2014
comment
@DTSCode: простое удаление его в деструкторе приведет к недопустимой семантике копии класса. Вам понадобится дополнительная работа или интеллектуальный указатель (или одно из лучших предложений в других ответах), чтобы исправить это. - person Mike Seymour; 11.07.2014

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

В черновом стандарте C++ есть следующее об инициализации базовых классов и переменных-членов:

12.6 Инициализация [class.init]

1 Если для объекта типа класса (возможно, с квалификацией cv) (или его массива) не указан инициализатор или инициализатор имеет форму (), объект инициализируется, как указано в 8.5.

И

12.6.1 Явная инициализация [class.expl.init]

1 Объект типа класса может быть инициализирован списком-выражений в скобках, где список-выражений рассматривается как список аргументов для конструктора, который вызывается для инициализации объекта. В качестве альтернативы, в качестве инициализатора можно указать одно выражение-присваивания, используя форму инициализации =. Применяется либо семантика прямой инициализации, либо семантика инициализации копирования; см. 8.5.

person R Sahu    schedule 11.07.2014