Откуда C ++ union узнает тип хранимого в нем и какой деструктор вызывать? - PullRequest
0 голосов
/ 16 февраля 2019

Работая с union из двух классов, в этом простом примере оказывается, что union запоминает последний сохраненный в нем класс и вызывает правильный деструктор для этого объекта:

#include <iostream>

using std::cout;
using std::endl;

struct yes{
    yes(){cout<<"yes-c"<<endl;}
    ~yes(){cout<<"yes-d"<<endl;}
};
struct no{
    no(){cout<<"no-c"<<endl;}
    ~no(){cout<<"no-d"<<endl;}
};
struct u{
    union{
        yes y;
        no n;
    };
    u(yes _y):y(_y){}
    u(no _n):n(_n){}        
    ~u(){}
};


int main() {
    yes y;
    no n;
    {
    u uu(n);
    }

    return 0;
}

Вывод:

yes-c
no-c
no-d
no-d
yes-d

Таким образом, uu вызовет правильный деструктор ~no() для объединения, как если бы он записывал тип при создании объединения.Как это работает?

Ответы [ 4 ]

0 голосов
/ 16 февраля 2019

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

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

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

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

Программа, демонстрирующая использованиеa union

Начиная с источника из других ответов, я расширил этот источник, чтобы продемонстрировать разницу между union членом struct и struct членом struct.

В приведенном ниже коде для конструкторов union, содержащих класс u, и для класса yes, и для класса no.Кроме того, у меня есть конструктор по умолчанию, конструктор копирования и оператор присваивания для класса u.

В дополнение к двум указанным вами структурам yes и no я также предоставилтретий struct, place, который не используется в union, а является просто другой переменной-членом.

Каждый из различных классов / структур имеет статический счетчик, который подсчитывается каждый раз, когдапостроено.

Я предоставил оператор присваивания для struct из u, чтобы правильно обработать присвоение.Оператор присваивания по умолчанию представлял собой побитовую копию, и я хотел сохранить уникальный идентификатор для каждого экземпляра для надлежащего документирования отслеживания строительства и разрушения.

union против structи содержащий создание / разрушение объекта

Обратите внимание, что в выводе, сгенерированном этой программой, когда создается class, содержащий union, структура place не создается, однако ни одна из union членов построено.И когда class, содержащий union, разрушается, структура place разрушается, однако ни один из union членов не разрушается.

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

Таким образом, в программе есть такая простая область действия с парой операторов.

{
    std::cout << "scope in" << std::endl;
    std::cout << "  create uu from n" << std::endl;
    u uu(n);
    std::cout << "  assign y to uu" << std::endl;
    uu = y;
    std::cout << "  assign n to uu" << std::endl;
    uu = n;
    std::cout << "scope out" << std::endl;
}

И раздел вывода, который я аннотировал несколькими примечаниями, соответствующими этому разделу источника:

scope in
  create uu from n
   no-cc : 0 -> 1                        ---  create a temporary struct no for the call to construct
   no-cc : 1 -> 2                        --- assignment of temporary to union through copy construction
   place-c : 1                           --- construct non-union struct member
 u-c - no: 1 place 1  tu: 2  tu_no: 2
   no-d : 1                               --- destruct the temporary passed to the constructor
  assign y to uu
   yes-cc : 0 -> 1                        ---  create a temporary struct yes for the assignment
   yes-cc : 1 -> 2                        --- assignment of temporary to union through copy construction
   place-c : 2                            --- construct non-union struct member
 u-c - yes: 2 place 2  tu: 1  tu_yes: 2   --- construct temporary union member using yes version of the constructors
   yes-d: 1                               --- destruct the temporary used in assignment
 u-= : 2 into 1 o.place 2  tu: 1  tu_yes: 2
 u-d : 2  tu: 1  tu_yes: 2                --- destruct temporary union struct
   place-d : 2                            --- destruct non-union struct member
  assign n to uu
   no-cc : 0 -> 3                        ---  create a temporary struct no for the assignment
   no-cc : 3 -> 4                       --- assignment of temporary to union through copy construction
   place-c : 3
 u-c - no: 3 place 3  tu: 2  tu_no: 4   --- construct temporary union using no version of constructors
   no-d : 3                             --- destruct the struct no temporary
u-= : 3 into 1 o.place 3  tu: 2  tu_no: 4
 u-d : 3  tu: 2  tu_no: 4               --- destruct the struct union temporary
   place-d : 3
scope out

с последующим уничтожением экземпляра u, созданного в области видимости при выходе переменнойобласти действия с:

 u-d : 1  tu: 2  tu_no: 2
   place-d : 1

Обратите внимание, что у нас есть экземпляр no, идентификатор экземпляра 2 и экземпляр yes, идентификатор экземпляра 2, что мы никогда не видим сообщение деструктора.Это временные файлы, которые на самом деле хранятся в union.Обратите внимание, что каждый экземпляр члена place разрушается, так как разрушается его контейнер u.

Исходный код и выходные данные для union пример

Исходный код этой демонстрационной программы:

// union_test.cpp : This file contains the 'main' function. Program execution begins and ends there.
//  /12049433/otkuda-c-union-uznaet-tip-hranimogo-v-nem-i-kakoi-destruktor-vyzyvat

#include "pch.h"
#include <iostream>

struct yes {
    yes() : n(nc++) { std::cout << "   yes-c: " << n << std::endl; }
    yes(yes const& o) : n(nc++)
    {
        std::cout << "   yes-cc : " << o.n << " -> " << n << '\n';
    }
    ~yes() { std::cout << "   yes-d: " << n << std::endl; }
    int n = 0;        // identifier number for specific instance
    static int nc;    // count of number of instances of this class created, instance identifier
};

struct no {
    no() : n(nc++) { std::cout << "   no-c : " << n << std::endl; }
    no(no const& o) : n(nc++)
    {
        std::cout << "   no-cc : " << o.n << " -> " << n << '\n';
    }

    ~no() { std::cout << "   no-d : " << n << std::endl; }
    int n = 0;        // identifier number for specific instance
    static int nc;    // count of number of instances of this class created, instance identifier
};

struct place {
    place() : n(nc++) { std::cout << "   place-c : " << n << std::endl; }
    place(no const& o) : n(nc++)
    {
        std::cout << "   place-cc : " << o.n << " -> " << n << '\n';
    }

    ~place() { std::cout << "   place-d : " << n << std::endl; }
    int n = 0;        // identifier number for specific instance
    static int nc;    // count of number of instances of this class created, instance identifier
};
struct u {
    union {
        yes y;
        no n;
    };
    place p;          // non-union to see construction/destruction of non-union.
    int nu = 0;       // identifier number for specific instance
    int tu = 0;       // indicator as to type currently in the union
    static int nc;    // count of number of instances of this class created, instance identifier
    u() : nu(nc++) { std::cout << " u-c : " << nu << " place " << p.n << "  tu: " << tu << std::endl; }
    u(yes _y) :y(_y), nu(nc++), tu(1) {
        std::cout << " u-c - yes: " << nu << " place " << p.n << "  tu: " << tu;
        switch (tu) {
        case 0:
            std::cout << std::endl;
            break;
        case 1:
            std::cout << "  tu_yes: " << y.n << std::endl;
            break;
        case 2:
            std::cout << "  tu_no: " << y.n << std::endl;
            break;
        default:
            std::cout << "Unknown  tu: " << tu << std::endl;
            break;
        }
    }
    u(no _n) :n(_n), nu(nc++), tu(2) {
        std::cout << " u-c - no: " << nu << " place " << p.n << "  tu: " << tu;
        switch (tu) {
        case 0:
            std::cout << std::endl;
            break;
        case 1:
            std::cout << "  tu_yes: " << y.n << std::endl;
            break;
        case 2:
            std::cout << "  tu_no: " << y.n << std::endl;
            break;
        default:
            std::cout << "Unknown  tu: " << tu << std::endl;
            break;
        }
    }
    u(u const& o) : nu(nc++), tu(o.tu)
    {
        std::cout << "u-cc : " << o.nu << " -> " << nu << "  tu: " << tu << " o.place " << o.p.n;
        switch (tu) {
        case 0:
            std::cout << std::endl;
            break;
        case 1:
            std::cout << "  tu_yes: " << o.y.n << std::endl;
            break;
        case 2:
            std::cout << "  tu_no: " << o.n.n << std::endl;
            break;
        default:
            std::cout << "Unknown  tu: " << tu << std::endl;
            break;
        }
    }
    u & operator = (const u & o)
    {
        std::cout << "u-= : " << o.nu << " into " << nu << " o.place " << o.p.n << "  tu: " << o.tu;
        tu = o.tu;
        switch (o.tu) {
        case 0:
            std::cout << std::endl;
            break;
        case 1:
            std::cout << "  tu_yes: " << o.y.n << std::endl;
            break;
        case 2:
            std::cout << "  tu_no: " << o.n.n << std::endl;
            break;
        default:
            std::cout << "Unknown  tu: " << tu << std::endl;
            break;
        }
        return *this;
    }

    ~u() {
        std::cout << " u-d : " << nu << "  tu: " << tu;
        switch (tu) {
        case 0:
            std::cout << std::endl;
            break;
        case 1:
            std::cout << "  tu_yes: " << y.n << std::endl;
            break;
        case 2:
            std::cout << "  tu_no: " << n.n << std::endl;
            break;
        default:
            std::cout << "Unknown" << std::endl;
            break;
        }
    }
};

int yes::nc = 0;
int no::nc = 0;
int place::nc = 0;
int u::nc = 0;


int main()
{
    std::cout << "create uu" << std::endl;
    u  xx;
    std::cout << "create y" << std::endl;
    yes y;
    std::cout << "create n" << std::endl;
    no n;
    {
        std::cout << "scope in" << std::endl;
        std::cout << "  create uu from n" << std::endl;
        u uu(n);
        std::cout << "  assign y to uu" << std::endl;
        uu = y;
        std::cout << "  assign n to uu" << std::endl;
        uu = n;
        std::cout << "scope out" << std::endl;
    }

    xx = y;

    return 0;
}

И вывод, сгенерированный этой программой.

create uu
   place-c : 0
 u-c : 0 place 0  tu: 0
create y
   yes-c: 0
create n
   no-c : 0
scope in
  create uu from n
   no-cc : 0 -> 1
   no-cc : 1 -> 2
   place-c : 1
 u-c - no: 1 place 1  tu: 2  tu_no: 2
   no-d : 1
  assign y to uu
   yes-cc : 0 -> 1
   yes-cc : 1 -> 2
   place-c : 2
 u-c - yes: 2 place 2  tu: 1  tu_yes: 2
   yes-d: 1
u-= : 2 into 1 o.place 2  tu: 1  tu_yes: 2
 u-d : 2  tu: 1  tu_yes: 2
   place-d : 2
  assign n to uu
   no-cc : 0 -> 3
   no-cc : 3 -> 4
   place-c : 3
 u-c - no: 3 place 3  tu: 2  tu_no: 4
   no-d : 3
u-= : 3 into 1 o.place 3  tu: 2  tu_no: 4
 u-d : 3  tu: 2  tu_no: 4
   place-d : 3
scope out
 u-d : 1  tu: 2  tu_no: 2
   place-d : 1
   yes-cc : 0 -> 3
   yes-cc : 3 -> 4
   place-c : 4
 u-c - yes: 4 place 4  tu: 1  tu_yes: 4
   yes-d: 3
u-= : 4 into 0 o.place 4  tu: 1  tu_yes: 4
 u-d : 4  tu: 1  tu_yes: 4
   place-d : 4
   no-d : 0
   yes-d: 0
 u-d : 0  tu: 1  tu_yes: -858993460
   place-d : 0
0 голосов
/ 16 февраля 2019

Краткий ответ: нет.

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

Сначала вы создаете объект n.Затем, когда вы передаете его по значению в конструктор u, он один раз копируется в аргумент _n.Этот объект _n затем копируется в элемент uu.n.

Разрушения имеют аргумент _n в конструкторе u и объект n в функции main.


Вот ваша программа с небольшими изменениями для добавления конструктора копирования и отслеживания объектов no:

#include <iostream>

struct yes{
    yes(){std::cout<<"yes-c"<<std::endl;}
    ~yes(){std::cout<<"yes-d"<<std::endl;}
};
struct no{
    no(){std::cout<<"no-c : "<<n<<std::endl;}
    no(no const& o)
        : n(o.n + 1)
    {
        std::cout << "no-cc : " << o.n << " -> " << n << '\n';
    }

    ~no(){std::cout<<"no-d : "<<n<<std::endl;}
    int n = 0;
};
struct u{
    union{
        yes y;
        no n;
    };
    u(yes _y):y(_y){}
    u(no _n):n(_n){}
    ~u(){}
};

int main()
{
    yes y;
    no n;
    {
        u uu(n);
    }
}

Без оптимизации или исключения копирования это будетсоздать вывод

yes-c
no-c : 0
no-cc : 0 -> 1
no-cc : 1 -> 2
no-d : 1
no-d : 0
yes-d

вывод no-c : 0 предназначен для создания объекта n в функции main.

вывод no-cc : 0 -> 1 предназначен для копирования вu аргумент конструктора _n.

Выход no-cc : 1 -> 2 предназначен для копирования аргумента _n в объект союзов n.

Выход no-d : 1- уничтожение аргумента _n.

Вывод no-d : 0 - уничтожение объекта n в функции main.

0 голосов
/ 16 февраля 2019

Нет, union не "знает", что это активный участник.У нас есть std::variant для этой работы.То, что вы видите, является результатом временного создания в конструкторе для вашего union.

при создании объединения нетривиальных типов.Вы очень вероятно попадете в ловушку вызова Undefined Behavior, поскольку ваша программа становится более сложной;std::variant хорошо помечает ваш профсоюз. И он соответствующим образом управляет жизнями

0 голосов
/ 16 февраля 2019

union s никогда не «запоминают», какое поле активно.

Ваш код выводит:

yes-c
no-c
no-d
no-d
yes-d

Похоже, что вы интерпретируете третью строку (no-d) в качестве деструктора вызовите поле объединения вашего класса, но здесь это не так.~u() не собирается вызывать деструкторы для y, ни n.

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

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