Nano Hash - криптовалюты, майнинг, программирование

Зачем явно вызывать конструктор в C++

Я знаю, что мы можем явно вызвать конструктор класса в C++, используя оператор разрешения области видимости, то есть className::className(). Мне было интересно, где именно мне нужно сделать такой звонок.

06.05.2009

  • Неправильно говорить, что вы можете вызывать конструктор напрямую. В стандарте явно указано (12.1/1): конструкторы не имеют имен. Вы можете вызывать конструктор только через другие конструкции, такие как приведение стиля функции или новое размещение. 06.05.2009

Ответы:


1

Чаще всего в конструкторе дочернего класса требуются некоторые параметры:

class BaseClass
{
public:
    BaseClass( const std::string& name ) : m_name( name ) { }

    const std::string& getName() const { return m_name; }

private:

    const std::string m_name;

//...

};


class DerivedClass : public BaseClass
{
public:

    DerivedClass( const std::string& name ) : BaseClass( name ) { }

// ...
};

class TestClass : 
{
public:
    TestClass( int testValue ); //...
};

class UniqueTestClass 
     : public BaseClass
     , public TestClass
{
public:
    UniqueTestClass() 
       : BaseClass( "UniqueTest" ) 
       , TestClass( 42 )
    { }

// ...
};

... Например.

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

06.05.2009
  • C++ неявно вызывает конструктор родительских классов при создании экземпляра производного класса, но он вызывает конструктор по умолчанию, если только вы сами явно не вызываете конкретный конструктор родительского класса в списке инициализаторов. 06.05.2009
  • Да, в моем примере я убедился, что единственный допустимый конструктор для BaseClass требует некоторых параметров. Я не помню случая, когда действительно требовался бы явный вызов конструктора по умолчанию. Может в виртуальном наследовании? 06.05.2009

  • 2

    Вы также иногда явно используете конструктор для создания временного объекта. Например, если у вас есть класс с конструктором:

    class Foo
    {
        Foo(char* c, int i);
    };
    

    и функция

    void Bar(Foo foo);
    

    но у тебя нет Foo рядом, ты мог бы сделать

    Bar(Foo("hello", 5));
    

    Это как литье. Действительно, если у вас есть конструктор, принимающий только один параметр, компилятор C++ будет использовать этот конструктор для выполнения неявного приведения типов.

    Недопустимо вызывать конструктор для уже существующего объекта. То есть вы не можете сделать

    Foo foo;
    foo.Foo();  // compile error!
    

    не важно, что ты предпримешь. Но вы можете вызвать конструктор без выделения памяти — для этого и предназначен placement new.

    char buffer[sizeof(Foo)];      // a bit of memory
    Foo* foo = new(buffer) Foo();  // construct a Foo inside buffer
    

    Вы даете new немного памяти, и он создает объект в этом месте вместо выделения новой памяти. Такое использование считается злом и редко встречается в большинстве типов кода, но распространено во встроенном коде и коде структур данных.

    Например, std::vector::push_back использует эту технику для вызова конструктора копирования. Таким образом, нужно сделать только одну копию вместо создания пустого объекта и использования оператора присваивания.

    06.05.2009
  • +1 за размещение новых. Это странно, но может быть полезно, если вы знаете, что делаете. 06.05.2009
  • Действительно, если у вас есть конструктор, принимающий только один параметр, компилятор C++ будет использовать этот конструктор для выполнения неявного приведения типов. - вот почему многие люди по умолчанию помещают ключевое слово в конструкторы с одним аргументом и снимают его только в том случае, если они уверены, что хотят неявного приведения типа параметра к типу класса. 06.05.2009
  • -1 Вы не вызываете конструктор. Синтаксис <typename>(ctor-arg list), и, если быть педантичным, это не то же самое, что <typename>::<typename>(ctor-arg list). Синтаксис просто создает впечатление, что вы вызываете конструктор. На самом деле вы никогда не вызываете конструктор как функцию. 14.03.2011
  • @LightnessRacesinOrbit Что на самом деле происходит, когда вы делаете <typename>::<typename> <var-name>(ctor-arg list)? 28.04.2013
  • @AdamSchnitzer: Многие вещи, включая выделение памяти, базовую инициализацию и инициализацию членов. 28.04.2013

  • 3

    Я думаю, что сообщение об ошибке компилятора C2585 дает наилучшую причину, по которой вам действительно нужно использовать оператор разрешения области действия в конструкторе, и это соответствует ответу Чарли:

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

    Итак, представьте, что у вас есть BaseClass, и BaseClassA, и BaseClassB оба наследуют BaseClass, а затем DerivedClass наследует и BaseClassA, и BaseClassB.

    Если вы выполняете преобразование или перегрузку оператора для преобразования DerivedClass в BaseClassA или BaseClassB, вам нужно будет определить, какой конструктор (я думаю, что-то вроде конструктора копирования, IIRC) использовать в преобразовании.

    06.05.2009

    4

    Как правило, вы не вызываете конструктор напрямую. Новый оператор вызывает его для вас, или подкласс вызывает конструкторы родительского класса. В C++ гарантируется, что базовый класс будет полностью сконструирован до запуска конструктора производного класса.

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

    06.05.2009

    5

    Я не думаю, что вы обычно используете это для конструктора, по крайней мере, не так, как вы описываете. Однако вам это понадобится, если у вас есть два класса в разных пространствах имен. Например, чтобы указать разницу между этими двумя искусственными классами, Xml::Element и Chemistry::Element.

    Обычно имя класса используется с оператором разрешения области видимости для вызова функции родительского класса унаследованного класса. Итак, если у вас есть класс Dog, который наследуется от Animal, и оба этих класса определяют функцию Eat() по-разному, может возникнуть случай, когда вы захотите использовать версию eat для животных на объекте Dog с именем «someDog». Мой синтаксис C++ немного заржавел, но я думаю, что в этом случае вы бы сказали someDog.Animal::Eat().

    06.05.2009

    6

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

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

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

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

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

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

    Размещение new нарушено во многом; при построении/уничтожении массивов - это один случай, поэтому я его не использую.

    13.08.2010

    7

    Рассмотрим следующую программу.

    template<class T>
    double GetAverage(T tArray[], int nElements)
    {
    T tSum = T(); // tSum = 0
    
    for (int nIndex = 0; nIndex < nElements; ++nIndex)
    {
        tSum += tArray[nIndex];
    }
    
    // Whatever type of T is, convert to double
    return double(tSum) / nElements;
    }
    

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

    11.12.2013
    Новые материалы

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

    Как написать эффективное резюме
    Предложения по дизайну и макету, чтобы представить себя профессионально Вам не позвонили на собеседование после того, как вы несколько раз подали заявку на работу своей мечты? У вас может..

    Частный метод Python: улучшение инкапсуляции и безопасности
    Введение Python — универсальный и мощный язык программирования, известный своей простотой и удобством использования. Одной из ключевых особенностей, отличающих Python от других языков, является..

    Как я автоматизирую тестирование с помощью Jest
    Шутка для победы, когда дело касается автоматизации тестирования Одной очень важной частью разработки программного обеспечения является автоматизация тестирования, поскольку она создает..

    Работа с векторными символическими архитектурами, часть 4 (искусственный интеллект)
    Hyperseed: неконтролируемое обучение с векторными символическими архитектурами (arXiv) Автор: Евгений Осипов , Сачин Кахавала , Диланта Хапутантри , Тимал Кемпития , Дасвин Де Сильва ,..

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

    Обеспечение масштабируемости LLM: облачный анализ с помощью AWS Fargate и Copilot
    В динамичной области искусственного интеллекта все большее распространение получают модели больших языков (LLM). Они жизненно важны для различных приложений, таких как интеллектуальные..