C ++ скопировать выбор полей - PullRequest
2 голосов
/ 15 января 2020

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

Пример кода:

#include <iostream>

struct A {
    bool x;
    A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
    B(const A &a) : a(a) {
        std::cout << "B constructed" << std::endl;
    }
    B(const B &other) : a(other.a) {
        std::cout << "B copied" << std::endl;
    }
    B(B &&other) : a(other.a) {
        std::cout << "B moved" << std::endl;
    }
    B &operator=(const B &other) {
        std::cout << "B reassigned" << std::endl;
        if (this != &other) {
            a = other.a;
        }
        return *this;
    }
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}

Я компилирую с: g++ -std=c++17 test.cpp -o test.exe

вывод:

A constructed
A copied
B constructed
1

B построен на месте. Почему нет? Я бы, по крайней мере, ожидал, что он будет сконструирован с перемещением, но вместо этого он будет скопирован.

Есть ли способ также построить A на месте, внутри B, которое будет возвращено? Как?

Ответы [ 3 ]

5 голосов
/ 15 января 2020

Создание B из A включает в себя копирование A - так говорится в вашем коде. Это не имеет ничего общего с разрешением копирования в возвратах функций, все это происходит в (возможной) конструкции B. Ничто в стандарте не позволяет исключить (как при «нарушении правила« как будто ») конструкцию копии в списках инициализации членов. См. [class.copy.elision] для нескольких обстоятельств, в которых правило «как будто» может быть нарушено.

Другими словами: вы получаете точно такой же результат при создании B b{A{true}};. Функция return точно так же хороша, но не лучше.

Если вы хотите, чтобы A было перемещено, а не скопировано, вам нужен конструктор B(A&&) (который затем перемещает-создает элемент a) .

3 голосов
/ 15 января 2020

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

Хотя язык пытается ограничить создание («материализацию») временных (таким образом, как это предусмотрено стандартом и не требует) не влияет на правило «как если бы»), бывают случаи, когда ваши временные должны быть материализованы и включают в себя:

[class.temporary]/2.1: - при привязке ссылки к prvalue

Вы делаете это здесь, в аргументе конструктора.

Фактически, если вы посмотрите на пример программы в этом абзаце стандарта он почти такой же, как и у вас, и описывает, как временную не нужно создавать в main, а затем копировать в новую временную переменную, которая входит в аргумент вашей функции… но временная - это создано для этого аргумента функции. Обойти это невозможно.

Копирование члену происходит в обычном порядке. Теперь вступает в силу правило «как будто», и в этом правиле просто нет исключения, позволяющего изменять семантику конструктора B (включая представление вывода "copied") так, как вы надеялись.

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

Конечно, если вы добавите B(A&& a) : a(std::move(a)) {}, тогда вы переместите объект в элемент вместо этого, но я думаю, ты тоже это знаешь.

1 голос
/ 15 января 2020

Я понял, как делать то, что хотел.

Намерение было вернуть несколько значений из функции с минимальным количеством "работы".

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

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

Это работает:

#include <iostream>

struct A {
    bool x;
    explicit A(bool x) : x(x) {
        std::cout << "A constructed" << std::endl;
    }
    A(const A &other) : x(other.x) {
        std::cout << "A copied" << std::endl;
    }
    A(A &&other) : x(other.x) {
        std::cout << "A moved" << std::endl;
    }
    A &operator=(const A &other) {
        std::cout << "A reassigned" << std::endl;
        if (this != &other) {
            x = other.x;
        }
        return *this;
    }
};

struct B {
    A a;
};

B foo() {
    return B{A{true}};
}


int main() {
    B b = foo();
    std::cout << b.a.x << std::endl;
}

вывод:

A constructed
1

Ключ должен был удалить все конструкторы B. Это позволило агрегатная инициализация, которая, кажется, создает поле на месте. В результате копирование A исключается. Я не уверен, технически ли это считается копией.

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