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

21.11.2008 09:43:08
Использование thisOR autoв указанном контексте было бы интересным ключевым словом для будущих целей рефакторинга.
sergiol 29.12.2015 16:13:25
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 для справки.

1213
20.04.2018 08:48:50
На самом деле замечательные параметры по умолчанию делают очень чистый способ сделать то, что мы обычно выполняем, вызывая this () в C #
bobobobo 18.02.2010 22:53:43
Обратите внимание, что предлагаемое решение, не использующее C ++ 11, работает только в том случае, если класс для конструирования не имеет ни наследуемых, ни постоянных полей. Я не нашел способ инициализировать родительский класс и константные поля вне списка инициализации.
greydet 24.07.2013 15:09:19
@bobobobo Использование параметров по умолчанию компилирует их в вызывающую программу, так что это не очень чисто. Корректная перегрузка - это больше кода, но реализация инкапсулирует значения по умолчанию.
Eugene Ryabtsev 8.04.2014 10:31:50
Недостатком использования init () является то, что вы не можете объявить указатель или ref как const (так как в ref / pointer это const, а не то, на что он указывает), если вы не инициализируете его в конструкторе ().
locka 12.09.2014 10:45:21
@gen (кроме пропущенного второго двоеточия) Он создаст временный Foo, а затем немедленно откажется от него.
Jim Balter 13.09.2017 18:59:08

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

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

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

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

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

1
21.11.2008 09:49:40
Он спрашивает, может ли один конструктор вызвать другой. Java и C # позволяют это.
Jonathan 21.11.2008 09:50:31

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

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

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

8
21.11.2008 09:56:18

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

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

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};
109
8.11.2011 18:00:50
Но как это отличается от стандартного синтаксиса параметров по умолчанию?
Tomáš Zato - Reinstate Monica 14.09.2015 13:52:27
@ TomášZato Одна вещь, которую вы не можете сделать с параметрами по умолчанию, это использовать ваш параметр для вызова другого конструктора: SomeType(string const &s) { /*...*/ } SomeType(char const *pc) : SomeType(string(pc)) { /*...*/ }
Cyrille Ka 14.09.2015 18:14:10
@ TomášZato Другое отличие состоит в том, что с параметрами по умолчанию у вас есть только один конструктор, который вы должны сделать общедоступным, защищенным или закрытым, в то время как с двумя конструкторами, один из которых вызывает другой, вы можете ограничить доступ к одному из них без необходимости также ограничивать доступ. к другому.
Kaiserludi 22.01.2016 17:00:23
PS: конечно, вы также можете сделать это с частной функцией инициализации, которая вызывается несколькими конструкторами, но это не будет работать для списков инициализации.
Kaiserludi 22.01.2016 17:22:35
Это также отличается от значений по умолчанию, потому что вы можете изменить его без перекомпиляции кода, который использует библиотеку. Со значениями по умолчанию эти значения «запекаются» в вызове.
Rptx 19.03.2016 22:59:00

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

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

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

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

23
20.04.2018 08:51:36
Вы неправы. Вы можете вызвать конструктор того же класса. Будет определено, какой конструктор вызвать, используя его список аргументов. Выполнение B (int x, inty): B (x) сначала вызовет конструктор с сигнатурой B (int x).
Ruud Verhoef 29.11.2019 09:54:40
Да. Но я был прав в ноябре 2008 года, до публикации C ++ 11.
kchoose2 3.12.2019 20:42:50

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

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

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

41
16.08.2017 01:23:39
Хорошая точка зрения; большинство просто сказали "нет, ты не можешь". Я могу :). Я сделал это переключение обратно и использовал оригинальный ctor, чтобы решить, какой другой вызвать. Во время отладки объект можно увидеть во втором, все инициализируется, но возвращается к значениям по умолчанию при возврате. Имеет много смысла, когда вы думаете об этом.
ChiefTwoPencils 28.10.2013 08:05:36
Это не «вызов конструктора». Только место , куда вы можете «вызвать конструктор» непосредственно находится в CTOR-инициализаторе в C ++ 11. В этом примере вы создаете объект, представляющий собой другой котелок с рыбой. Не вводите в заблуждение тот факт, что это похоже на вызов функции для конструктора, потому что это не один! На самом деле нет никакого способа сделать вызов функции для конструктора, поэтому невозможно создать экземпляр класса, единственным конструктором (ами) которого являются экземпляры шаблона функции, аргументы шаблона которого не могут быть выведены.
Lightness Races in Orbit 26.11.2014 23:38:40
(То есть синтаксически невозможно явно предоставить аргументы шаблона конструктору.)
Lightness Races in Orbit 26.11.2014 23:40:04
На самом деле есть один способ вызвать функцию в конструкторе - использовать newсинтаксис размещения . Обычно это не то, что вы хотите. (И это ничего не дает, чтобы вы могли явно предоставить аргументы шаблона.)
celticminstrel 23.06.2015 02:23:30
использование размещения newвсе равно создаст новый объект, хотя и в том же месте памяти. Но, тем не менее, это другой объект, и можно собрать код, который это доказывает.
Leon 23.09.2015 15:17:25

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

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

14
10.06.2015 11:42:19
Кажется, это не то, что рекомендуется делать, как вы можете прочитать в конце 10.3 parashift.com/c++-faq-lite/ctors.html#faq-10.3
Stormenet 24.03.2011 20:05:25
Мне кажется, единственным недостатком этого является то, что это добавляет немного накладных расходов; new (this) проверяет, равен ли это == NULL, и пропускает конструктор, если это так.
Deadcode 12.11.2012 10:45:04
Это почти наверняка UB.
Lightness Races in Orbit 26.11.2014 23:42:07
Это действительно зло. Предположим, что вы выделяете память в этом конструкторе, а освобождаете ее в деструкторе. Никакая память не будет освобождена.
Elvedin Hamzagic 10.06.2015 10:21:39
Но вы все равно можете сбежать от катастрофы, если будете вызывать деструктор явно: this->~Foo();доnew (this) Foo();
Elvedin Hamzagic 10.06.2015 11:21:54

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

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

20
6.06.2015 13:11:31

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

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

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

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

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

4
25.11.2011 00:54:11

В 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: честно говоря, я был удивлен, что это не было упомянуто ранее.

4
20.02.2012 10:00:51
Я попробовал это на g ++ под Ubuntu (4.4.3). Это не сработало: в конструкторе 'Vertex :: Vertex ()': ошибка: недопустимое использование класса Vertex.
Kevin 10.08.2012 13:59:48
Я тестировал его под Visual Studio 2003. NET Architect Edition - работает нормально.
izogfif 31.10.2012 15:29:26
Этот метод очень опасен! Это вызывает утечку памяти, если члены не относятся к типу POD. Например, std :: string.
Alexander Drichel 11.06.2013 13:59:15
Честно говоря, я поражен и разочарован тем, что Visual C ++ позволяет это. Это очень сломано. Давайте не будем убеждать людей использовать эту стратегию.
Lightness Races in Orbit 26.11.2014 23:40:51
это похоже на размещение новых?
pqnet 21.10.2017 20:24:15

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

0
6.06.2015 13:15:57
Вы не можете "вызвать конструктор"; пожалуйста, смотрите мои комментарии к ответу ohlemacher. Однако вы, по сути, правы.
Lightness Races in Orbit 26.11.2014 23:46:19
Конструктор - это просто инициализатор, поэтому создание общего инициализатора вне конструктора - это метод старой версии. Память выделяется до того, как конструктор когда-либо вызывается, обычно, когда вызывается оператор new или malloc ...
Elvedin Hamzagic 10.06.2015 09:53:40

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

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

2
6.06.2015 13:18:13
Мне кажется, что использование метода друга не имеет преимущества перед обычным закрытым методом. Почему ты бы так поступил?
pqnet 21.10.2017 20:27:30

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

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

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

    // do the special things for a Foo with char
    ...
}
2
10.11.2015 21:18:53

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

#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

так :)

-1
9.09.2016 10:38:09
Это был не начальный вопрос, он спрашивает не о вызове конструктора базового класса, а о другом конструкторе из того же класса.
Actarus 3.10.2017 07:03:54

Проще говоря, вы не можете до 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 членов один раз и только один раз.

6
22.04.2018 17:22:40