Действительно ли при вызове конструктора пустого класса используется какая-либо память?

Предположим, у меня есть класс вроде

class Empty{
    Empty(int a){ cout << a; }
}

И затем я вызываю его, используя

int main(){
    Empty(2);
    return 0;
}

Приведет ли это к тому, что в стеке будет выделена какая-либо память для создания «пустого» объекта? Очевидно, что аргументы необходимо поместить в стек, но я не хочу нести никаких дополнительных накладных расходов. В основном я использую конструктор как статический член.

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

template <int which>
class FuncName{
    template <class T>
    FuncName(const T &value){
        if(which == 1){
            // specific behavior
        }else if(which == 2){
            // other specific behavior
        }
    }
};

что позволяет мне писать что-то вроде

int main(){
    int a = 1;
    FuncName<1>(a);
}

так что я могу специализировать один параметр шаблона, не указывая при этом тип T. Кроме того, я надеюсь, что компилятор оптимизирует другие ветки внутри конструктора. Если кто-нибудь знает, правда ли это или как это проверить, мы будем очень признательны. Я также предположил, что добавление шаблонов в ситуацию не меняет проблему "пустого класса" сверху, верно?


person Victor Liu    schedule 06.01.2010    source источник
comment
Вопрос в том, почему это вас волнует. Задача компилятора - заботиться и генерировать лучший код. Вам следует сконцентрироваться на написании наиболее выразительного кода.   -  person Martin York    schedule 07.01.2010
comment
PS. Нет необходимости помещать аргумент в стек. C ++ ABI намеренно не определен, чтобы компиляторы имели возможность использовать регистр для передачи параметров, если это делает код более эффективным.   -  person Martin York    schedule 07.01.2010
comment
Мне не все равно, потому что я хочу максимально возможную производительность; Я действительно ненавижу позицию, согласно которой код должен быть элегантным, и не беспокоиться об этих вещах. Иногда эти вещи имеют значение (я занимаюсь высокопроизводительными вычислениями).   -  person Victor Liu    schedule 07.01.2010
comment
PPS. При специализации шаблона нет необходимости в if в приведенном выше шаблоне.   -  person Martin York    schedule 07.01.2010
comment
@Victor: Тогда есть вещи поважнее, чем использование объекта стека (который не требует затрат) по сравнению с операцией ветвления в вашем шаблоне, которая может вызвать остановку процессора. Причина, по которой код причины должен быть элегантным, заключается не в том, что нам нравится неприятный код, а скорее в том, что элегантный код обычно лучше оптимизируется компилятором. Если вы действительно заботитесь о производительности, вам следует беспокоиться о вещах, которые не имеют значения, а не о микрооптимизации. Я действительно ненавижу утверждение, что создание макросов оптимизации - это хорошо, потому что я настоящий программист, настоящий оптимизатор пишет компилятор (меня).   -  person Martin York    schedule 07.01.2010


Ответы (3)


Цитата Страуструпа:

Почему размер пустого класса не равен нулю? Чтобы адреса двух разных объектов были разными. По той же причине «новый» всегда возвращает указатели на отдельные объекты. Учитывать:

class Empty { };

void f()
{
    Empty a, b;
    if (&a == &b) cout << "impossible: report error to compiler supplier";

    Empty* p1 = new Empty;
    Empty* p2 = new Empty;
    if (p1 == p2) cout << "impossible: report error to compiler supplier";
}   

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

struct X : Empty {
    int a;
    // ...
};

void f(X* p)
{
    void* p1 = p;
    void* p2 = &p->a;
    if (p1 == p2) cout << "nice: good optimizer";
}

Эта оптимизация безопасна и может быть очень полезной. Это позволяет программисту использовать пустые классы для представления очень простых концепций без дополнительных затрат. Некоторые современные компиляторы предоставляют такую ​​«оптимизацию пустого базового класса».

person Richard Pennington    schedule 06.01.2010
comment
Но что, если на созданный объект вообще не ссылаются? Разрешено ли компилятору либо не резервировать для него место в стеке, либо немедленно очищать его, даже если он не выходит за пределы области видимости? - person Victor Liu; 07.01.2010
comment
Да, я уверен, что компилятор полностью удалит зарезервированную память, если она не используется или не упоминается. Это довольно простая оптимизация. Конечно, в зависимости от компилятора. - person Richard Pennington; 07.01.2010
comment
Пока поведение не изменяется, компилятор может исключить всю концепцию объекта под капотом, и все это может быть встроено в код и все значения, хранящиеся в регистрах. - person Martin York; 07.01.2010
comment
Этот ответ не имеет отношения к вопросу - person anatolyg; 05.01.2016

Может, может, и нет, в зависимости от обстоятельств. Если вы скажете:

Empty e;
Empty * ep = & e;

тогда очевидно, что вещи должны быть распределены.

person Community    schedule 06.01.2010
comment
Обязательно ли компилятору выдавать код выделить что-то, чтобы дать ему адрес? Есть ли причина, по которой & e не может быть адресом, который не является ни кучей, ни стеком, и где нет фактического объекта? - person James; 07.01.2010
comment
@Autopulated: я так считаю, см. Комментарий Ричарда Пеннингтона. Более интересный вопрос, если вы никогда не спрашиваете его адрес; тогда он еще должен быть? (Вопрос в стиле дзен) - person Victor Liu; 07.01.2010
comment
Если вы берете адрес e, а затем делаете что-то с этим адресом (чего нет в моем примере кода), тогда e должен быть создан, то есть выделен. - person ; 07.01.2010
comment
Например, если вы сравниваете указатели на два разных объекта типа Empty, они не должны быть равны. Таким образом, компилятор должен предоставить им разные адреса. Независимо от того, находятся ли они на самом деле в стеке или нет, это выходит за рамки языка, но очевидно, что на практике компилятор должен назначать им адреса. Даже если бы это было незафиксированное пространство, действительно ли мы думаем, что 1 байт незафиксированного адресного пространства дешевле выделить, чем 1 байт стека? Если вы никогда не возьмете адрес, компилятор знает, что вы никогда не воспользуетесь им, а некоторые компиляторы иногда игнорируют объект. - person Steve Jessop; 07.01.2010

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

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

void empty1 ( int x )
{
    using namespace std;

    int a;
    Empty e1 ( x );
    int b;

    cout << endl;
    cout << "empty1" << endl;
    cout << hex << int ( &x ) << " " << dec << ( &x - &a ) << endl;
    cout << hex << int ( &a ) << " " << dec << ( &a - &b ) << endl;
}

а затем попробуйте запустить это по сравнению с empty8 функцией с восемью созданными пустыми пустотами. С g ++ на x86, если вы берете адрес любой из пустых, вы получаете местоположение между x и a в стеке, следовательно, включаете x в вывод. Вы не можете предположить, что хранилище для объектов окажется в том же порядке, в каком они объявлены в исходном коде.

person Pete Kirkham    schedule 06.01.2010
comment
Как? Я смотрю на листинг сборки g ++ в качестве простого примера, и мне очень трудно следовать. Я использую g++ -c -g -O2 -Wa,-ahl=file.s file.cpp - person Victor Liu; 07.01.2010
comment
Вы можете лучше скомпилировать исполняемый файл (с отладочной информацией), а затем использовать objdump -dS и искать интересующие вас исходные строки. Поскольку у вас есть оптимизация, помните, что одна и та же исходная строка может появляться в нескольких местах в разборке. - person Steve Jessop; 07.01.2010