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

703

Как разработчик С# я использую для запуска конструкторов:

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

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

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

Есть ли способ сделать это на С++?

Я попробовал назвать имя класса и использовать ключевое слово 'this', но оба они не работают.

  • 0
    Использование this ИЛИ auto в указанном контексте было бы интересным ключевым словом для будущих целей рефакторинга.
Теги:
constructor

15 ответов

996
Лучший ответ

С++ 11: Да!

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

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

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

С++ 03: Нет

К сожалению, в С++ 03 нет возможности сделать это, но есть два способа имитации этого:

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

    class Foo {
    public:
      Foo(char x, int y=0);  // combines two constructors (char) and (char, int)
      // ...
    };
    
  • Используйте метод 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 для справки.

  • 72
    На самом деле замечательные параметры по умолчанию делают очень чистый способ сделать то, что мы обычно выполняем, вызывая this () в C #
  • 5
    Обратите внимание, что предлагаемое решение, не использующее C ++ 11, работает только в том случае, если класс для конструирования не имеет ни наследуемых, ни постоянных полей. Я не нашел способ инициализировать родительский класс и константные поля вне списка инициализации.
Показать ещё 15 комментариев
102

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

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

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};
  • 2
    Но как это отличается от стандартного синтаксиса параметров по умолчанию?
  • 0
    @ TomášZato Одна вещь, которую вы не можете сделать с параметрами по умолчанию, это использовать ваш параметр для вызова другого конструктора: SomeType(string const &s) { /*...*/ } SomeType(char const *pc) : SomeType(string(pc)) { /*...*/ }
Показать ещё 3 комментария
37

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

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

Ссылка: https://isocpp.org/wiki/faq/ctors#init-methods

  • 2
    Хорошая точка зрения; большинство просто сказали "нет, ты не можешь". Я могу :). Я сделал это переключение назад и использовал исходный ctor, чтобы решить, кому позвонить. Во время отладки объект можно увидеть во втором, все инициализируется, но возвращается к значениям по умолчанию при возврате. Имеет много смысла, когда вы думаете об этом.
  • 8
    Это не "вызов конструктора". Единственное место, где вы можете напрямую вызвать конструктор - это инициализатор ctor в C ++ 11. В этом примере вы создаете объект, представляющий собой другой котелок с рыбой. Не вводите в заблуждение тот факт, что это похоже на вызов функции для конструктора, потому что это не один! Фактически нет никакого способа сделать вызов функции для конструктора, поэтому невозможно создать экземпляр класса, единственными конструкторами которого являются экземпляры шаблона функции, аргументы шаблона которого не могут быть выведены.
Показать ещё 3 комментария
18

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

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

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

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

17

В С++ 11 конструктор

12

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

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 намного лучше.

  • 4
    Кажется, что это не то, что рекомендуется делать, как вы можете прочитать в конце 10.3 parashift.com/c++-faq-lite/ctors.html#faq-10.3
  • 0
    Мне кажется, единственным недостатком этого является то, что это добавляет немного накладных расходов; new (this) проверяет, равен ли это == NULL, и пропускает конструктор, если это так.
Показать ещё 8 комментариев
7

Нет, в С++ вы не можете вызвать конструктор из конструктора. То, что вы можете сделать, как указал Уоррен, - это:

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

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

5

В Visual С++ вы также можете использовать эту нотацию внутри конструктора: 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 С++ 2003 и 2008. Вы можете также назвать конструкторы несколько, я полагаю, так же, как в Java и С#.

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

  • 0
    Я попробовал это на g ++ под Ubuntu (4.4.3). Это не сработало: в конструкторе 'Vertex :: Vertex ()': ошибка: недопустимое использование класса Vertex.
  • 0
    Я тестировал его под Visual Studio 2003. NET Architect Edition - работает нормально.
Показать ещё 12 комментариев
2

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

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

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

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

  • 0
    Мне кажется, что использование метода друга не имеет преимущества перед обычным закрытым методом. Почему ты бы так поступил?
2

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

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

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

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

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

2

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

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

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

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

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

  • 1
    Он спрашивает, может ли один конструктор вызвать другой. Java и C # позволяют это.
  • 1
    правильно - это не возможно в C ++
1

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

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

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

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

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

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

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

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

1

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

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

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

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

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

  • 1
    Вы не можете "вызвать конструктор"; пожалуйста, смотрите мои комментарии к ответу ohlemacher. Однако вы, по сути, правы.
  • 0
    Конструктор - это просто инициализатор, поэтому создание общего инициализатора вне конструктора - это метод старой версии. Память выделяется до того, как конструктор когда-либо вызывается, обычно, когда вызывается оператор new или malloc ...
0

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

#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 = С++ 98 -o test_1

вы увидите:

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

так:)

  • 2
    Это был не начальный вопрос, он спрашивает не о вызове конструктора базового класса, а о другом конструкторе из того же класса.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню