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

avatar
Stormenet
21 ноября 2008 в 09:43
523566
15
1018

Как разработчик на 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, но оба ответа не дали результата.

Источник
sergiol
29 декабря 2015 в 16:13
0

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

Ответы (15)

avatar
JohnIdol
21 ноября 2008 в 10:04
1357

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. Используйте метод инициализации для совместного использования общего кода:

    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 ++ для справки.

bobobobo
18 февраля 2010 в 22:53
84

На самом деле замечательные параметры по умолчанию делают очень чистый способ делать то, что мы обычно выполняем, вызывая this () в C #

greydet
24 июля 2013 в 15:09
5

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

sud03r
8 августа 2013 в 08:23
0

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

Eugene Ryabtsev
8 апреля 2014 в 10:31
8

@bobobobo Использование параметров по умолчанию компилирует их в вызывающую программу, так что это не очень чисто. Перегрузка - это больше кода, правильно, но реализация инкапсулирует значения по умолчанию.

locka
12 сентября 2014 в 10:45
4

Единственным недостатком использования init () является то, что вы не можете объявить указатель или ссылку, которая является константой (как в ref / pointer is const, а не то, на что он указывает), если вы не инициализируете ее в конструкторе ().

raidsan
6 декабря 2014 в 15:03
0

«Делегирование конструкторов» --- Visual Studio 2013 поддерживает эти функции, см .: msdn.microsoft.com/en-us/library/hh567368.aspx

PC Luddite
11 августа 2015 в 08:04
0

@bobobobo C # поддерживает параметры по умолчанию в VS2010 (C # 4.0). См. Здесь: msdn.microsoft.com/en-us/library/dd264739.aspx

gen
8 июля 2016 в 08:17
0

@JohnIdol, как насчет того, чтобы написать: Foo:Foo(char x) { Foo(x, int(x)+7); } Что это будет делать?

Tristan
21 сентября 2016 в 21:09
0

Какой порядок исполнения?

Phil1970
28 января 2017 в 15:02
0

@gen Это точно не будет работать должным образом (если когда-нибудь компилируется).

Guy Avraham
12 августа 2017 в 14:28
0

Считается ли вызов аргумента Ctor из копии Ctor (в C ++ 11) «хорошей практикой» ИЛИ следует ли этого избегать?

Jim Balter
13 сентября 2017 в 18:59
3

@gen (кроме отсутствующего второго двоеточия) Он создаст временный Foo, а затем немедленно его отбросит.

Samuel
21 ноября 2017 в 07:45
0

Я падаю на это каждый раз. IDE - 2017, перенацеленный компилятор - 2012, который не может этого сделать, черт возьми.

Jamie S
1 апреля 2018 в 01:27
1

@locka Есть хитрый способ обойти это, используя оператор запятой, который позволяет вам вызывать функцию init () в списке инициализаторов. К сожалению, это требует, чтобы другой член был инициализирован отдельно от списка инициализаторов, что может быть нежелательно.

val is still with Monica
30 апреля 2018 в 21:33
0

Где мне разместить решение C ++ 11? В .hpp или .cpp?

srm
18 июня 2018 в 19:00
0

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

ShadowRanger
17 января 2020 в 14:59
0

@JamieS: Это хакерское решение обычно более производительно, когда рассматриваемые члены являются типами классов с нетривиальными затратами на создание и копирование; подход, описанный в этом ответе, означает, что классы создаются по умолчанию, а затем копируются в init; хакерский подход C ++ 03 и подход C ++ 11 улучшают это, позволяя создать правильную окончательную версию ровно один раз (именно поэтому они работают с const и ссылками).

Jouny
4 февраля 2020 в 15:55
0

@JohnIdol, а как насчет new (this) Foo(...), это должно сработать.

Sebastian
16 июня 2020 в 15:17
0

А как насчет шаблонного конструктора как еще одной возможности?

pooya13
7 января 2021 в 21:24
0

Можно ли проверить параметр перед тем, как решить, как вызвать другой конструктор?

avatar
user8683714
28 октября 2017 в 11:53
12

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

avatar
gyula
9 сентября 2016 в 10:38
-1

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

#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

итак :)

Actarus
3 октября 2017 в 07:03
2

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

avatar
V15I0N
10 ноября 2015 в 21:18
2

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

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

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

    // do the special things for a Foo with char
    ...
}
avatar
Pantelis Sopasakis
2 ноября 2014 в 00:19
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();
}

Однако в 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();
}

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

pqnet
21 октября 2017 в 20:27
0

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

avatar
XPD
6 января 2014 в 10:52
0

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

Lightness Races in Orbit
26 ноября 2014 в 23:46
1

Вы не можете «вызвать конструктор»; пожалуйста, посмотрите мои комментарии к ответу Олемахера. Однако вы, по сути, правы.

Elvedin Hamzagic
10 июня 2015 в 09:53
0

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

avatar
izogfif
20 февраля 2012 в 10:00
5

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

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

Kevin
10 августа 2012 в 13:59
0

Я пробовал это на g ++ под Ubuntu (4.4.3). Не сработало: В конструкторе «Vertex :: Vertex ()»: ошибка: недопустимое использование «класса Vertex».

izogfif
31 октября 2012 в 15:29
0

Я тестировал его в редакции Visual Studio 2003 .NET Architect - работает нормально.

Alexander Drichel
11 июня 2013 в 13:59
2

Этот метод очень опасен! Это вызывает утечку памяти, если члены не из POD-типа. Например std :: string.

Lightness Races in Orbit
26 ноября 2014 в 23:40
5

Честно говоря, я поражен и разочарован тем, что Visual C ++ позволяет это. Он очень сломан. Давайте не будем убеждать людей использовать эту стратегию.

pqnet
21 октября 2017 в 20:24
0

это похоже на размещение нового?

Ben Voigt
18 марта 2018 в 17:37
0

@LightnessRacesinOrbit: кажется, он должен создать временный объект, такой же, как Vertex(-1, -1). Единственная разница заключается в использовании квалифицированного поиска вместо неквалифицированного, но он по-прежнему называет тот же класс.

Lightness Races in Orbit
18 марта 2018 в 18:48
0

@BenVoigt: А что с this->?

Ben Voigt
18 марта 2018 в 18:53
0

@LightnessRacesinOrbit: C ++ позволяет искать статические члены класса, используя синтаксис доступа к члену (в качестве альтернативы обычному оператору classname + scope). Не могу вспомнить, применимо ли это также к вложенным типам и определениям типов. Правило «введенного имени класса» означает, что имя класса можно найти, как если бы оно было именем члена.

Lightness Races in Orbit
18 марта 2018 в 18:54
0

@BenVoigt: конструктор не является статическим членом класса; это «особая функция-член». Этот синтаксис - полная чушь.

Ben Voigt
18 марта 2018 в 18:54
0

@LightnessRacesinOrbit: назван не конструктор (у него нет имени), а сам тип. Через введенное имя класса.

Lightness Races in Orbit
18 марта 2018 в 18:55
0

@BenVoigt: Тогда мы вернемся к this->, что не имеет смысла. Это недопустимый C ++, точка.

Ben Voigt
18 марта 2018 в 18:55
0

Однако я думаю, что вложенные типы нельзя найти через доступ к членам, иначе я бы написал c.iterator it = c.begin() вместо std::vector<Blah>::iterator it = c.begin();

Lightness Races in Orbit
18 марта 2018 в 18:56
0

@BenVoigt: Да, именно так.

Ben Voigt
18 марта 2018 в 19:00
0

Это печально, потому что this->typename T varname; было бы очень удобно для доступа к зависимому имени в CRTP.

avatar
e.James
25 ноября 2011 в 00:54
7

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

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

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

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

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

avatar
Ben L
22 сентября 2011 в 20:07
22

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

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

avatar
lyngvi
24 марта 2011 в 16:53
17

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

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

Stormenet
24 марта 2011 в 20:05
4

Кажется, это не рекомендуется делать, поскольку вы можете прочитать в конце 10.3 parashift.com/c++-faq-lite/ctors.html#faq-10.3

Deadcode
12 ноября 2012 в 10:45
0

Мне кажется, что единственным недостатком этого является то, что это добавляет немного накладных расходов; new (this) проверяет, является ли this == NULL, и пропускает конструктор, если это так.

Lightness Races in Orbit
26 ноября 2014 в 23:42
2

Это почти наверняка UB.

holgac
13 января 2015 в 01:46
0

Разве это теоретически не вызывает конструкторы полей более одного раза? Компиляторы могут это исправить, но вы не можете этому доверять. Кроме того, хотя это не очень хорошая идея, у меня могут быть некоторые члены класса в Foo, которые изменяют статические / глобальные переменные в своем конструкторе, поэтому я would ожидаю, что они проделают эту операцию дважды с этим кодом.

Elvedin Hamzagic
10 июня 2015 в 10:21
4

Это действительно зло. Предположим, что вы выделяете память в этом конструкторе и освобождаете ее в деструкторе. Никакая память не будет освобождена.

Elvedin Hamzagic
10 июня 2015 в 11:21
2

Но вы все равно можете избежать катастрофы, если явно вызовете деструктор: this->~Foo();, до new (this) Foo();

lyngvi
10 июня 2015 в 11:46
0

@ElvedinHamzagic: Хорошее замечание (Holgac тоже это понял); обновленный пост.

Stormenet
10 июня 2015 в 13:34
0

@lyngvi не может редактировать мой предыдущий комментарий ... Во всяком случае, это новое местоположение, похоже: isocpp.org/wiki/faq/dtors#placement-new

Ben Voigt
18 марта 2018 в 17:35
0

Помните об ограничениях на замену такого объекта - у вас не может быть нестатических элементов данных, которые являются const или ссылками.

Patrick Parker
3 января 2020 в 01:53
0

меньше зла, назначить *this = Foo();

avatar
sqqqrly
12 августа 2009 в 15:31
49

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

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

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

ChiefTwoPencils
28 октября 2013 в 08:05
3

Хорошая точка зрения; большинство просто сказали «нет, ты не можешь». Я могу :). Я сделал это переключение обратно и использовал исходный ctor, чтобы решить, кому еще позвонить. При отладке объект можно увидеть во втором, все инициализируется, но при возврате возвращается к значениям по умолчанию. Если подумать, в этом есть большой смысл.

Lightness Races in Orbit
26 ноября 2014 в 23:38
11

Это не «вызов конструктора». только место, где вы можете «вызвать конструктор» напрямую, находится в инициализаторе ctor в C ++ 11. В этом примере вы создаете объект, который представляет собой другой котел с рыбой. Пусть вас не вводит в заблуждение тот факт, что он выглядит как вызов функции конструктора, потому что он , а не ! Фактически нет способа вызвать функцию к конструктору, поэтому невозможно построить экземпляр класса, единственный конструктор (ы) которого являются экземплярами шаблона функции, аргументы шаблона которого не могут быть выведены.

Lightness Races in Orbit
26 ноября 2014 в 23:40
1

(То есть синтаксически невозможно явно предоставить конструктору аргументы шаблона.)

celticminstrel
23 июня 2015 в 02:23
0

На самом деле есть один способ вызвать функцию к конструктору - использовать синтаксис размещения new. Однако обычно это не то, что вам нужно. (И он ничего не делает, чтобы вы могли явно указать аргументы шаблона.)

Leon
23 сентября 2015 в 15:17
0

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

avatar
kchoose2
22 ноября 2008 в 06:36
29

C ++ 11 : Да!

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

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

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

C ++ 03 : Нет

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

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

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

Ruud Verhoef
29 ноября 2019 в 09:54
0

Ты неправ. Вы можете вызвать конструктор того же класса. Будет определен, какой конструктор вызвать, используя его список аргументов. Выполнение B (int x, inty): B (x) сначала вызовет конструктор с подписью B (int x).

kchoose2
3 декабря 2019 в 20:42
16

да. Но я был прав в ноябре 2008 года, до того, как был опубликован C ++ 11.

avatar
Cyrille Ka
21 ноября 2008 в 10:00
125

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

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

class SomeType
{
  int number;

public:
  SomeType(int newNumber) : number(newNumber) {}
  SomeType() : SomeType(42) {}
};
Tomáš Zato - Reinstate Monica
14 сентября 2015 в 13:52
3

Но чем это отличается от стандартного синтаксиса параметров по умолчанию?

Cyrille Ka
14 сентября 2015 в 18:14
0

@ TomášZato Одна вещь, которую вы не можете сделать с параметрами по умолчанию, - это использовать ваш параметр для вызова другого конструктора: SomeType(string const &s) { /*...*/ } SomeType(char const *pc) : SomeType(string(pc)) { /*...*/ }

Kaiserludi
22 января 2016 в 17:00
7

@ TomášZato Еще одно отличие состоит в том, что с параметрами по умолчанию у вас есть только один конструктор, который вы должны сделать общедоступным, защищенным или частным, а с двумя конструкторами, один из которых вызывает другой, вы можете ограничить доступ к одному из них, не ограничивая также доступ к другому.

Kaiserludi
22 января 2016 в 17:22
0

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

Rptx
19 марта 2016 в 22:59
2

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

avatar
unwind
21 ноября 2008 в 09:56
12

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

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

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

avatar
warren
21 ноября 2008 в 09:49
1

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

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

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

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

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

Jonathan
21 ноября 2008 в 09:50
2

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