Объявить объект без вызова конструктора по умолчанию - PullRequest
2 голосов
/ 21 июня 2019

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

Предполагая, что есть некоторый класс Foo, в Java я мог бы написать Foo bar;, чтобы объявить экземпляр Foo без инициализации bar. Однако в C ++, когда я пишу Foo bar;, bar инициализируется вызовом конструктора по умолчанию класса Foo.

Это особенно неприятно, если я написал один или несколько конструкторов для класса Foo, каждый из которых имеет хотя бы один аргумент. В этом случае код не будет скомпилирован с ошибкой, подобной no matching function for call to 'Foo::Foo()'

Например, скажем, у меня есть следующее определение класса в Java:

public class Foo {
    private boolean b;

    Foo(boolean b) { this.b = b; }
}

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

class Foo {
        bool b_;
    public:
        Foo(bool b) : b_(b) {}
};

В Java я мог бы написать какой-нибудь метод

public static Foo makeFoo(int x) {
    Foo result;
    if (x > 10) { result = new Foo(true); }
    else { result = new Foo(false); }
    return result;
}

Однако, если я напишу аналогичный метод на C ++, я получу ошибку компиляции:

Foo makeFoo(int x) {
    Foo result; // here, a call is made to Foo::Foo() which doesn't exist, and thus compilation fails
    if (x > 10) { 
        result = Foo(true); // this would probably also fail since I didn't specify a copy-constructor, but I'm not entirely sure
    }
    else {
        result = Foo(false); // as above, I think this would probably fail
    }
    return result;
}

Хотя приведенный мною пример бесполезен, я часто использовал такой подход при написании кода Java. Есть ли способ эмулировать это поведение в C ++? Или это просто плохой дизайн? Если да, то какой подход вы бы порекомендовали?

Ответы [ 3 ]

5 голосов
/ 21 июня 2019

Если вы не хотите использовать указатели для получения справочной функциональности, как объяснил Игорь (и другие) в первом комментарии к вашему вопросу, тогда вы можете сделать пару вещей.

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

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

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

Foo makeFoo(int x) {
    if (x > 10) { 
        Foo result = Foo(true);
        return result;
    }
    else {
        Foo result = Foo(false);
        return result;
    }
}

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

Foo premakeFoo(int x) {
    if (x > 10) { 
        Foo result = Foo(true);
        return result;
    }
    else {
        Foo result = Foo(false);
        return result;
    }
}

Foo makeFoo(int x) {
    Foo result = premakeFoo(x);
    // common post init code can go here.
    return result;
}

Если вы не хотите, чтобы это было совершенно отдельной функцией, вы можете сделать лямбду.

Foo makeFoo(int x) {
    Foo result = ([=]() {
    if (x > 10) { 
        Foo result = Foo(true);
        return result;
    }
    else {
        Foo result = Foo(false);
        return result;
    })();
    // common post init code can go here.
    return result;
}

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

Foo makeFoo(int x) {
    Foo result = (x > 10) ? Foo(true) : Foo(false); // or just Foo(x>10)
    // common post init code can go here.
    return result;
}

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

3 голосов
/ 21 июня 2019

В Java, когда вы делаете Foo result;, вы создаете ссылку на Foo, вы фактически не создаете объект.C ++ отличается тем, что имеет семантику значений, а Foo result; фактически создает объект.Если конструктор по умолчанию отсутствует, возникает ошибка.

Чтобы получить такой же тип поведения, вам нужно использовать указатели в C ++.Вы не можете использовать ссылку, так как ссылка должна быть привязана к объекту при его создании, в отличие от Java.Итак, если вы используете std::unique_ptr<Foo>, вы можете объявить это, а затем инициализировать его позже с std::unique_ptr<Foo>, который вы инициализируете в своей логике создания.Используя ваш пример, код будет выглядеть так:

Foo makeFoo(int x) {
    std::unique_ptr<Foo> result; // here result doesn't have an actual object yet
    if (x > 10) { 
        result = std::make_unique<Foo>(true); // now we pass the values we want to construct with and get a valid object
    }
    else {
        result = std::make_unique<Foo>(false); // same as above
    }
    return *result; // this lets you return a `Foo` which will be a copy, and the Foo created by make_unique will be destroyed
}

Если вы не хотите делать копию, вы можете вместо этого использовать return result; и изменить функцию так, чтобы она возвращала std::unique_ptr<Foo>. * 1012.*

1 голос
/ 21 июня 2019

то, что вы пытаетесь сделать, называется фабричным шаблоном и обычно реализуется таким образом

std::unique_ptr<Foo> makeFoo(int x) {
    std::unique_ptr<Foo> result = nullptr; 

    if (x > 10) { 
        result = std::make_unique<Foo>(true); 
    }
    else {
        result = std::make_unique<Foo>(false); 
    }
    return result;
}

, чтобы вышесказанное работало, вам также нужно включить заголовок.обратитесь к this для получения дополнительной информации об unique_ptr и управлении памятью в c ++

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

std::unique_ptr<Foo> makeFoo(int x) {
    if (x > 10) { 
        return std::make_unique<Foo>(true); 
    }
    return std::make_unique<Foo>(false); 
}
...