Параметр c ++ как const T vs const T & - PullRequest
       66

Параметр c ++ как const T vs const T &

0 голосов
/ 02 августа 2020

предположим, что SomeDataStruct является «огромным», создаст ли современный компилятор эквивалентный и, следовательно, столь же эффективный код в следующих двух случаях?

1) void fooByValue(const SomeDataStruct data);   

2) void fooByReference(const SomeDataStruct& data);

если эквивалент, какая идиома «предпочтительнее», почему?

обратите внимание, что этот вопрос похож на этот:

https://softwareengineering.stackexchange.com/questions/372105/is-passing-arguments-as-const-references-premature-optimization

, но не идентичен, так как здесь я использую "const "в обеих функциях; в указанной ссылке fooByValue просто

1) void fooByValue(SomeDataStruct data);

изменить: предположим, что тип SomeDataStruct не имеет конструктора копирования.

Ответы [ 2 ]

0 голосов
/ 02 августа 2020

предположим, что SomeDataStruct «огромен», создаст ли современный компилятор эквивалентный и, следовательно, столь же эффективный код в следующих двух случаях?

Как вы определили объект, это зависит от компилятора , что происходит с объектом в функции. Давайте узнаем, что делает g cc на x86-64 Linux. Взгляните на следующий код:

struct big_
{
    unsigned long w,x,y,z;
    int pos;
};

unsigned long byval(const big_ big)
{
    auto y = big.z;
    y += big.y;
    y += big.x;
    y += big.w;
    return y;
}

unsigned long byref(const big_& big)
{
    auto y = big.z;
    y += big.y;
    y += big.x;
    y += big.w;
    return y;
}

Компиляция с помощью g++ -std=c++17 -O3 дает мне следующую сборку Godbolt link :

byval(big_):
        mov     rax, QWORD PTR [rsp+24]
        add     rax, QWORD PTR [rsp+32]
        add     rax, QWORD PTR [rsp+16]
        add     rax, QWORD PTR [rsp+8]
        ret
byref(big_ const&):
        mov     rax, QWORD PTR [rdi+16]
        add     rax, QWORD PTR [rdi+24]
        add     rax, QWORD PTR [rdi+8]
        add     rax, QWORD PTR [rdi]
        ret

Что мы можем увидеть в приведенном выше коде?

Аргументы первой функции byval передаются в стек. Аргументы второй функции передаются через регистр rdi (см. Соглашения о вызовах для вашей ОС, чтобы узнать, почему). Передача чего-либо через регистр всегда быстрее, чем передача в стек, потому что регистры ближе к cpu, а стек находится где-то в кэше или оперативной памяти. Следовательно, здесь лучше передавать по ссылке. Если у вас есть небольшой объект (8 байт), лучше передать его по значению, потому что он все равно будет передан через регистр. Передача по стеку происходит только тогда, когда ваш объект настолько велик, что не может поместиться в регистр.

Еще мы видим, что byval имеет параметр, отличный от const. Компилятор просто сбросил его.

, поскольку я здесь использую «const» в обеих функциях;

Это не имеет значения, как вы можете видеть из объяснений выше.

void fooByValue(SomeDataStruct data); изменить: предположим, что тип SomeDataStruct не имеет конструктора копирования.

Если вы не писали конструктор копирования, это не означает, что компилятор может не генерировать его неявно. Но предположим, вы его удалили. Так что теперь вы не можете его скопировать, и если вы попытаетесь, ваш код не скомпилируется. Однако, если вы определили конструктор перемещения, вы можете переместить его.

void fn()
{
    fooByValue( std::move(data) ); // no copy
}

Использование const не повлияет на производительность вашего кода. const похож на контракт между вами и компилятором. Если вы отметите что-то как const, компилятор не позволит вам это изменить. Если вы как-то его измените, это приведет к неопределенному поведению. Я предлагаю вам go и прочитать эту статью Артура О'Дуайера об этом топе c const - это контракт.

0 голосов
/ 02 августа 2020

Верхний уровень const (не за указателем / ссылкой) не влияет на объявление.

// Even if the declaration uses const:
void fooByValue(const SomeDataStruct data);

// The definition without `const` is still considered a valid definition for that declaration.
// It's not a different overload!
// (this oddity is inherited from C, where overloading is a compiler error but this was always allowed)
void fooByValue(SomeDataStruct data)
{
   data.member = 0;
}

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

Соответствующая часть стандарта C ++ - 9.3.3.5 Функции:

Тип функции определяется по следующим правилам. [..] После создания списка типов параметров любые cv-квалификаторы верхнего уровня, изменяющие тип параметра, удаляются при формировании типа функции.

...