Можно ли вызвать конструктор из другого конструктора (сделать цепочку конструктора) в 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)
    {
      // ...
    }
    

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

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

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

class SomeType
{
  int number;

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

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

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

Ссылка: https://isocpp.org/wiki/faq/ctors

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

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

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

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

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

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) {}
};

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

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

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++ 11.

C++ 11 представляет делегирующие конструкторы:

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

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

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

Делегирующие конструкторы не могут быть рекурсивными.

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

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

Нет, в 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);
    }
};

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

В 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#.

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

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

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

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

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

Я бы предложил использовать 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 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();
}

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

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

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

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

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

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

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

Было бы проще проверить, чем решить:) Попробуйте это:

#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

так:)

Другие вопросы по тегам