Могу ли я вызвать конструктор из другого конструктора (цепочки конструкторов) в C++?

Как C# разработчик я привык работать через конструкторы:

class Test {
    public Test() {
        DoSomething();
    }

    public Test(int count) : this() {
        DoSomethingWithCount(count);
    }

    public Test(int count, string name) : this(count) {
        DoSomethingWithName(name);
    }
}

есть ли способ сделать это в C++?

Я попытался вызвать имя класса и использовать ключевое слово "this", но оба не удалось.

15 ответов


C++11: Да!

C++11 и далее имеет эту же функцию (называется делегирование конструкторов).

синтаксис немного отличается от C#:

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {}
};

C++03: Нет

к сожалению, нет способа сделать это в C++03, но есть два способа моделирования этого:

  1. вы можете объединить два (или более) конструкторы по умолчанию параметры:

    class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
    
  2. используйте метод init для совместного использования общего кода:

    class Foo {
    public:
      Foo(char x);
      Foo(char x, int y);
      // ...
    private:
      void init(char x, int y);
    };
    
    Foo::Foo(char x)
    {
      init(x, int(x) + 7);
      // ...
    }
    
    Foo::Foo(char x, int y)
    {
      init(x, y);
      // ...
    }
    
    void Foo::init(char x, int y)
    {
      // ...
    }
    

посмотреть запись FAQ на C++ для справки.


нет, вы не можете вызвать один конструктор из другого, в C++03 (так называемое делегирование) конструктор.

это изменилось в C++11 (он же C++0x), который добавил поддержку следующего синтаксиса:
(пример взят из Википедия)

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};

Я считаю, что вы можете вызвать конструктор из конструктора. Он будет компилироваться и запускаться. Недавно я видел, как кто-то это сделал, и он работал как на Windows, так и на Linux.

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

Ref: https://isocpp.org/wiki/faq/ctors#init-methods


стоит отметить, что вы can вызов конструктора родительского класса в конструкторе, например:

class A { /* ... */ };

class B : public A
{
    B() : A()
    {
        // ...
    }
};

но, нет, вы не можете вызвать другой конструктор того же класса.


на C++11, a конструктор может вызвать другую перегрузку конструктора:

class Foo  {
     int d;         
public:
    Foo  (int i) : d(i) {}
    Foo  () : Foo(42) {} //New to C++11
};

кроме того, члены также могут быть инициализированы следующим образом.

class Foo  {
     int d = 5;         
public:
    Foo  (int i) : d(i) {}
};

Это должно устранить необходимость создания вспомогательного метода инициализации. И все же рекомендуется не вызывать виртуальные функции в конструкторах или деструкторах, чтобы избежать использования элементов, которые не могут быть инициализированы.


Если вы хотите быть злым, вы можете использовать оператор "new" на месте:

class Foo() {
    Foo() { /* default constructor deliciousness */ }
    Foo(Bar myParam) {
      new (this) Foo();
      /* bar your param all night long */
    } 
};

Кажется, работает для меня.

редактировать

Как указывает @ElvedinHamzagic, если Foo содержит объект, который выделил память, этот объект не может быть освобожден. Это еще больше все усложняет.

более общий пример:

class Foo() {
private:
  std::vector<int> Stuff;
public:
    Foo()
      : Stuff(42)
    {
      /* default constructor deliciousness */
    }

    Foo(Bar myParam)
    {
      this->~Foo();
      new (this) Foo();
      /* bar your param all night long */
    } 
};

выглядит немного менее элегантно, наверняка. Решение @ JohnIdol намного лучше.


нет, в C++ вы не можете вызвать конструктор из конструктора. Что вы можете сделать, как указал Уоррен, так это:

  • перегрузите конструктор, используя разные подписи
  • используйте значения по умолчанию для аргументов, чтобы сделать "более простую" версию доступной

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


в Visual C++ вы также можете использовать эту нотацию внутри конструктора: this - >Classname:: Classname(параметры другого конструктора). См. пример ниже:

class Vertex
{
 private:
  int x, y;
 public:
  Vertex(int xCoo, int yCoo): x(xCoo), y(yCoo) {}
  Vertex()
  {
   this->Vertex::Vertex(-1, -1);
  }
};

Я не знаю, работает ли он где-то еще, я тестировал его только в Visual C++ 2003 и 2008. Вы также можете позвонить несколько конструкторы таким образом, я полагаю, так же, как в Java и C#.

С. П.: честно говоря, я был удивлен, что это не было упомянуто ранее.


Если я правильно понимаю ваш вопрос, вы спрашиваете, можете ли вы вызвать несколько конструкторов на C++?

Если это то, что вы ищете, то нет - это невозможно.

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

вы даже можете иметь один конструктор с аргументами по умолчанию в конце.

но вы не можете иметь несколько конструкторы, а затем вызвать каждый из них отдельно.


еще один вариант, который еще не был показан, - разделить ваш класс на два, обернув легкий класс интерфейса вокруг исходного класса, чтобы достичь эффекта, который вы ищете:

class Test_Base {
    public Test_Base() {
        DoSomething();
    }
};

class Test : public Test_Base {
    public Test() : Test_Base() {
    }

    public Test(int count) : Test_Base() {
        DoSomethingWithCount(count);
    }
};

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


Я бы предложил использовать private friend метод, который реализует логику приложения конструктора и вызывается различными конструкторами. Вот пример:

предположим, что у нас есть класс StreamArrayReader С некоторыми частными полями:

private:
    istream * in;
      // More private fields

и мы хотим определить два конструктора:

public:
    StreamArrayReader(istream * in_stream);
    StreamArrayReader(char * filepath);
    // More constructors...

где второй просто использует первый (и, конечно, мы не хотим дублировать реализацию первого). В идеале, хотелось бы сделать что-то вроде:

StreamArrayReader::StreamArrayReader(istream * in_stream){
    // Implementation
}

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    StreamArrayReader(&instream);
    instream.close();
}

однако это не разрешено в C++. По этой причине мы можем определить метод private friend следующим образом, который реализует то, что должен делать первый конструктор:

private:
  friend void init_stream_array_reader(StreamArrayReader *o, istream * is);

теперь этот метод (потому что это друг) имеет доступ к закрытым полям o. Затем первым конструктором становится:

StreamArrayReader::StreamArrayReader(istream * is) {
    init_stream_array_reader(this, is);
}

обратите внимание, что это не создает несколько копий для создаваемой копии. Второй становится:

StreamArrayReader::StreamArrayReader(char * filepath) {
    ifstream instream;
    instream.open(filepath);
    init_stream_array_reader(this, &instream);
    instream.close();
}

то есть вместо того, чтобы один конструктор вызывал другой, оба звонят частному другу!


этот подход может работать для некоторых классов (когда оператор присваивания ведет себя "хорошо"):

Foo::Foo()
{
    // do what every Foo is needing
    ...
}

Foo::Foo(char x)
{
    *this = Foo();

    // do the special things for a Foo with char
    ...
}

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


проще говоря, вы не можете до C++11.

в C++11 вводит делегирование конструкторов:

делегирующий конструктор

Если имя самого класса отображается как class-or-identifier в список инициализатора, список должен состоять из одного члена только инициализатор; такой конструктор известен как делегирование конструктор и конструктор, выбранный единственным членом список инициализаторов конструктор цели

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

делегирование конструкторов не может быть рекурсивным.

class Foo {
public: 
  Foo(char x, int y) {}
  Foo(int y) : Foo('a', y) {} // Foo(int) delegates to Foo(char,int)
};

обратите внимание, что делегирующий конструктор является предложением "все или ничего"; если конструктор делегирует другому конструктору, вызывающему конструктору не разрешается иметь никаких других члены в списке инициализации. Это имеет смысл, если вы думаете об инициализации членов const / reference один раз и только один раз.


было бы легче испытать, чем решить :) Попробуйте это:

#include <iostream>

class A {
public:
    A( int a) : m_a(a) {
        std::cout << "A::Ctor" << std::endl;    
    }
    ~A() {
        std::cout << "A::dtor" << std::endl;    
    }
public:
    int m_a;
};

class B : public A {
public:
    B( int a, int b) : m_b(b), A(a) {}
public:
    int m_b;
};

int main() {
    B b(9, 6);
    std::cout << "Test constructor delegation a = " << b.m_a << "; b = " << b.m_b << std::endl;    
    return 0;
}

и скомпилировать его с 98 std: G++ main.cpp-std=c++98-o test_1

вы увидите:

A::Ctor
Test constructor delegation a = 9; b = 6
A::dtor

так :)