RVO при преобразовании возвращаемого значения

Я борюсь со сложностью преобразований и приведения типов, и я не могу найти в Интернете совет, который четко гарантировал бы эффективное преобразование при возврате из функции. У меня есть два класса: Base и Derived, где Derived не имеет дополнительных элементов данных по сравнению с Base. У меня есть именованный конструктор для базового класса, который я хочу вернуть с помощью RVO и привести к объекту производного типа с минимальными накладными расходами.

class Base { 
public:
    static Base namedConstructor(int n){
        return Base(n);
    } 
protected:
    Base(int n) : member(n){
    }
    int member; 
};

class Derived : public Base  {
    static Derived nC2(int n) {
        Derived derived = namedConstructor(n); 
        // Error: no suitable user-defined conversion from "Base" to "Derived"... 
        // modify derived
        return derived; 
    } 
};

Есть ли способ исправить ошибку, который удовлетворяет всем следующим требованиям?

  1. Без использования RTTI. Динамическое литье кажется ненужным.
  2. Только одно определение namedConstructor на случай, если мне нужно его изменить. Если надо, могу сделать шаблон, но меня интересуют альтернативы.
  3. namedConstructor должен использовать RVO в nC2.
  4. Преобразование не должно автоматически завершаться ошибкой, если я добавляю или удаляю элементы данных либо из базового, либо из производного класса (это может сделать что-то похожее на reinterpret_cast).

person cinnamon    schedule 26.07.2020    source источник
comment
Вы не можете преобразовать объект в другой, подобный этому. Компилятор должен знать, как создать одно из другого. Объект Derived даже не может быть сконструирован, так как Base имеет частный конструктор.   -  person Manuel    schedule 26.07.2020
comment
Я сделал конструктор защищенным. Я не знаю, как решить другую вашу проблему. Конечно, я не могу конвертировать и возражать так, есть ошибка.   -  person cinnamon    schedule 26.07.2020


Ответы (2)


Добавьте private ctor. на Derived, который строит его из Base:

Derived(const Base& base) : Base(base) {
}

Если позже вы добавите в Derived дополнительные переменные-члены, инициализируйте и их.

Это соответствует стандарту (хотя и немного странно) и будет оптимизировано в релизных сборках.

person Dr. Gut    schedule 26.07.2020
comment
Спасибо, а как работает RVO в строке Derivedderived=namedConstructor(n);? Предположим, что в базе есть элемент данных большого массива, состоящий из n нулей. Сколько раз этот большой массив будет записан в стек или кучу в строке Derivedderived=namedConstructor(n);? Откуда вы знаете? - person cinnamon; 26.07.2020
comment
@cinnamon Объекты создаются непосредственно в хранилище, куда в противном случае они были бы скопированы/перемещены. Если есть исключение копирования, это означает один раз, непосредственно в целевом хранилище. - person Manuel; 26.07.2020
comment
@cinnamon: код оптимизирован достаточно хорошо, см. демонстрацию. MSVC вызывает только nC2, clang и gcc оптимизируют еще лучше. - person Dr. Gut; 26.07.2020

Я добавил конструкторы, и с g++ 3.0 есть исключение копирования:

class Base { 
public:
    static Base namedConstructor(int n){
        return Base(n);
    } 
protected:
    Base(int n) : member(n){
        std::cout << "Create Base" << std::endl;
    }
    Base (const Base & b) : member (b.member) {
        std::cout << "Copy Base" << std::endl;
    }
    int member; 
};

class Derived : public Base  {
public:
    Derived () : Base (0) {
        std::cout << "Create Derived" << std::endl;
    }
    Derived (const Base & b) : Base(b) {
        std::cout << "Create Derived from base" << std::endl;
    }
    int val () { return member; }
    static Derived nC2(int n) {
        return namedConstructor(n); //derived; 
    } 
};

int main ()
{
    Derived d = Derived::nC2(1);
    std::cout << "Value: " << d.val() << std::endl;
    return 0;
}

Выход из этого:

Create Base
Copy Base
Create Derived from base
Value: 1

так что одно создание Base, одна копия Base при создании Derived и самого Derived.

Нет копий из namedConstructor выхода или nC2, ни для задания в main.

Я скомпилировал это с -O0, но с такими маленькими функциями может быть здесь оптимизация, и они удалены.

person Manuel    schedule 26.07.2020
comment
Эта копия не кажется ненужной? Могу ли я не предположить, что производный класс и базовый класс будут отображаться в памяти одинаково? - person cinnamon; 26.07.2020
comment
возможно, я могу сформулировать этот вопрос по-другому: есть ли что-нибудь умное, что я могу сделать в направлении reinterpret_cast, которое по-прежнему удовлетворяет 4? - person cinnamon; 26.07.2020
comment
Вы не можете правильно интерпретировать базовый класс в производный, если только он не является указателем или ссылкой. Копия существует потому, что вы конвертируете базовый объект в производный. Вы не можете избежать этого с помощью метода, который вы запрашиваете. Копия находится в Derived (const Base & b) : Base(b) {, копия находится в Base(b), вы не можете избежать этого, чтобы преобразовать Base в Derived, работая с значениями вместо указателей или ссылок. - person Manuel; 26.07.2020