C++ Musings

Incoherent ramblings about C++

Returning by const reference for large objects is better than hoping for copy elision

If T is used over const T&, a copy has to be generated, even if the caller is only interested in a reference. If const T& is used and the caller only wants a reference, no copy is needed.

struct LargeStruct {
    int values[20480];
};

struct Container {
    LargeStruct s;

    const LargeStruct& ref() const { return s; }
    LargeStruct val() const { return s; }

};

int byValue(const Container& b, int i) {
    return b.val().values[i];
}

int byRef(const Container& b, int i) {
    return b.ref().values[i];
}

GCC 6.3 with -O3 and Clang with -O3:

byValue(Container const&, int):
  push rbx
  mov edx, 81920
  movsx rbx, esi
  mov rsi, rdi
  sub rsp, 81920
  mov rdi, rsp
  call memcpy
  mov eax, DWORD PTR [rsp+rbx*4]
  add rsp, 81920
  pop rbx
  ret
byRef(Container const&, int):
  movsx rsi, esi
  mov eax, DWORD PTR [rdi+rsi*4]
  ret

Defining textual constants as const char* or constexpr const char* is better than std::string

std::string does not store the text locally, but has to execute a new and delete at construction time, which const char* and constexpr const char* do not have to do.

#include <string>

namespace {
    std::string a = "asdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd";

    const char* b = "dgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdg";

    constexpr const char* c = "sdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf";
}

const char* fooA() { return a.c_str(); }
const char* fooB() { return b; }
const char* fooc() { return c; }

Clang 5.0.0 with -std=c++17 -O3

std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string(): # @std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
        mov     rax, rdi
        mov     rdi, qword ptr [rax]
        add     rax, 16
        cmp     rdi, rax
        je      .LBB0_1
        jmp     operator delete(void*)                  # TAILCALL
.LBB0_1:
        ret
fooA():                               # @fooA()
        mov     rax, qword ptr [rip + (anonymous namespace)::a[abi:cxx11]]
        ret
fooB():                               # @fooB()
        mov     eax, .L.str.2
        ret
fooc():                               # @fooc()
        mov     eax, .L.str.1
        ret
_GLOBAL__sub_I_example.cpp:             # @_GLOBAL__sub_I_example.cpp
        push    rax
        mov     qword ptr [rip + (anonymous namespace)::a[abi:cxx11]], (anonymous namespace)::a[abi:cxx11]+16
        mov     edi, 61
        call    operator new(unsigned long)
        mov     qword ptr [rip + (anonymous namespace)::a[abi:cxx11]], rax
        mov     qword ptr [rip + (anonymous namespace)::a[abi:cxx11]+16], 60
        movups  xmm0, xmmword ptr [rip + .L.str+44]
        movups  xmmword ptr [rax + 44], xmm0
        movups  xmm0, xmmword ptr [rip + .L.str+32]
        movups  xmmword ptr [rax + 32], xmm0
        movups  xmm0, xmmword ptr [rip + .L.str+16]
        movups  xmmword ptr [rax + 16], xmm0
        movups  xmm0, xmmword ptr [rip + .L.str]
        movups  xmmword ptr [rax], xmm0
        mov     qword ptr [rip + (anonymous namespace)::a[abi:cxx11]+8], 60
        mov     byte ptr [rax + 60], 0
        mov     edi, std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
        mov     esi, (anonymous namespace)::a[abi:cxx11]
        mov     edx, __dso_handle
        pop     rax
        jmp     __cxa_atexit            # TAILCALL
.L.str:
        .asciz  "asdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasdasd"

.L.str.1:
        .asciz  "sdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf"

.L.str.2:
        .asciz  "dgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdg"

GCC 7.3 with -std=c++17 -O3:

std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string():
        mov     rdx, QWORD PTR [rdi]
        lea     rax, [rdi+16]
        cmp     rdx, rax
        je      .L1
        mov     rdi, rdx
        jmp     operator delete(void*)
.L1:
        rep ret
fooA():
        mov     rax, QWORD PTR (anonymous namespace)::a[rip]
        ret
.LC0:
        .string "dgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdgdgfkdg"
fooB():
        mov     eax, OFFSET FLAT:.LC0
        ret
.LC1:
        .string "sdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdfsdf"
fooc():
        mov     eax, OFFSET FLAT:.LC1
        ret
_GLOBAL__sub_I__Z4fooAv:
        sub     rsp, 8
        mov     edi, 61
        mov     QWORD PTR (anonymous namespace)::a[rip], OFFSET FLAT:(anonymous namespace)::a+16
        call    operator new(unsigned long)
        movdqa  xmm0, XMMWORD PTR .LC2[rip]
        movabs  rcx, 8314036833820636001
        mov     QWORD PTR (anonymous namespace)::a[rip], rax
        mov     QWORD PTR (anonymous namespace)::a[rip+16], 60
        mov     edx, OFFSET FLAT:__dso_handle
        movups  XMMWORD PTR [rax], xmm0
        mov     QWORD PTR [rax+48], rcx
        mov     DWORD PTR [rax+56], 1685283172
        mov     esi, OFFSET FLAT:(anonymous namespace)::a
        mov     edi, OFFSET FLAT:std::__cxx11::basic_string<char, std::char_traits<char>, std::allocator<char> >::~basic_string()
        movdqa  xmm0, XMMWORD PTR .LC3[rip]
        mov     QWORD PTR (anonymous namespace)::a[rip+8], 60
        mov     BYTE PTR [rax+60], 0
        movups  XMMWORD PTR [rax+16], xmm0
        movdqa  xmm0, XMMWORD PTR .LC4[rip]
        movups  XMMWORD PTR [rax+32], xmm0
        add     rsp, 8
        jmp     __cxa_atexit
.LC2:
        .quad   8314036833820636001
        .quad   7017860981484380516
.LC3:
        .quad   7238236110174905459
        .quad   8314036833820636001
.LC4:
        .quad   7017860981484380516
        .quad   7238236110174905459