Reputation: 3003
I want to learn variable aliasing in C++ for make much code lesser than before without pay any cost (Zero Cost), for I can better describe my purpose, please see below details
I have a structure like below
struct Info
{
size_t d1;
size_t d2;
size_t d3;
size_t d4;
};
I use this struct in another class. (that call LargeName
class) like below
class LargeName
{
public:
// … many fields and methods
Info get_large_name_info() const
{
return info;
}
private:
Info info;
}
In this scenario if I want to use info fields from LargeName
instant in external function, I have to something like below
void foo(LargeName large_name)
{
large_name.get_large_name_info().d1;
large_name.get_large_name_info().d2;
large_name.get_large_name_info().d3;
large_name.get_large_name_info().d4;
}
As you can see in above example if my class name and getter access name of info is too long, I have to write very character to use d1 to d4. But I want to avoid them. so for example I can write below code to prevent that
void foo(LargeName large_name)
{
const Info& info = large_name.get_large_name_info();
info.d1;
info.d2;
info.d3;
info.d4;
}
As you can see my code is little cleaner and very shorter than before. (I called info
is alias variable for large_name.get_large_name_info()
) But the problem is I have not sure to compiler generate equal code for new version of my code that same as before.
I think second foo implementation my pay reference and dereferencing cost for using info variable.
I have two question:
First question is how to variable aliasing in C++ without pay any cost?
Note: The assembly version of two foo
function is different. I check with GCC version 5.4.0 with -std=c++11 -O3 -S
switch .
Second question is why compiler generate different code for those foo
method? on which situation these two codes (first foo
and second foo
) does not behave same exactly (I want to learn why compiler generate different code?) ? ( I think if two code is same exactly, then assembly generated must be same )
-------------------- APPENDIX -------------------
A full example:
Source code:
#include <cstdlib>
#include <iostream>
#include <array>
using namespace std;
class A
{
public:
int id;
std::array<size_t, 4> data;
float prec;
};
class User
{
public:
A get_a() const
{
return a;
}
private:
A a;
};
int main()
{
User user;
// scenario 1
#ifdef SC1
cout << "A id: " << user.get_a().id << endl;
cout << "A prec: " << user.get_a().prec << endl;
cout << "A data: ";
cout << user.get_a().data[0];
cout << user.get_a().data[1];
cout << user.get_a().data[2];
cout << user.get_a().data[3];
cout << endl;
#endif
// scenario 2
#ifdef SC2
const A& a = user.get_a();
cout << "A id: " << a.id << endl;
cout << "A prec: " << a.prec << endl;
cout << "A data: ";
cout << a.data[0];
cout << a.data[1];
cout << a.data[2];
cout << a.data[3];
cout << endl;
#endif
return EXIT_SUCCESS;
}
compile with g++ -std=c++11 -O3 -D=SC1 -S main.cpp
scenario 1:
.file "main.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "A id: "
.LC1:
.string "A prec: "
.LC2:
.string "A data: "
.section .text.unlikely,"ax",@progbits
.LCOLDB3:
.section .text.startup,"ax",@progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1495:
.cfi_startproc
pushq %rbx
.cfi_def_cfa_offset 16
.cfi_offset 3, -16
movl $6, %edx
movl $.LC0, %esi
movl $_ZSt4cout, %edi
subq $80, %rsp
.cfi_def_cfa_offset 96
movl 16(%rsp), %ebx
movq %fs:40, %rax
movq %rax, 72(%rsp)
xorl %eax, %eax
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
movl %ebx, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movss 56(%rsp), %xmm1
movl $8, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
movss %xmm1, 12(%rsp)
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
pxor %xmm0, %xmm0
movl $_ZSt4cout, %edi
cvtss2sd 12(%rsp), %xmm0
call _ZNSo9_M_insertIdEERSoT_
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $.LC2, %esi
movl $_ZSt4cout, %edi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movq 24(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 32(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 40(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq 48(%rsp), %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movl $_ZSt4cout, %edi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movq 72(%rsp), %rcx
xorq %fs:40, %rcx
jne .L5
addq $80, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 16
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 8
ret
.L5:
.cfi_restore_state
call __stack_chk_fail
.cfi_endproc
.LFE1495:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .text.unlikely
.LCOLDB4:
.section .text.startup
.LHOTB4:
.p2align 4,,15
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1689:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq $8, %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1689:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .text.unlikely
.LCOLDE4:
.section .text.startup
.LHOTE4:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
compile with g++ -std=c++11 -O3 -D=SC2 -S main.cpp
Scenario 2:
.file "main.cpp"
.section .rodata.str1.1,"aMS",@progbits,1
.LC0:
.string "A id: "
.LC1:
.string "A prec: "
.LC2:
.string "A data: "
.section .text.unlikely,"ax",@progbits
.LCOLDB3:
.section .text.startup,"ax",@progbits
.LHOTB3:
.p2align 4,,15
.globl main
.type main, @function
main:
.LFB1495:
.cfi_startproc
pushq %r14
.cfi_def_cfa_offset 16
.cfi_offset 14, -16
pushq %r13
.cfi_def_cfa_offset 24
.cfi_offset 13, -24
movl $6, %edx
pushq %r12
.cfi_def_cfa_offset 32
.cfi_offset 12, -32
pushq %rbp
.cfi_def_cfa_offset 40
.cfi_offset 6, -40
movl $.LC0, %esi
pushq %rbx
.cfi_def_cfa_offset 48
.cfi_offset 3, -48
movl $_ZSt4cout, %edi
subq $80, %rsp
.cfi_def_cfa_offset 128
movl 16(%rsp), %r14d
movss 56(%rsp), %xmm1
movss %xmm1, 12(%rsp)
movq 24(%rsp), %r13
movq 32(%rsp), %r12
movq %fs:40, %rax
movq %rax, 72(%rsp)
xorl %eax, %eax
movq 40(%rsp), %rbp
movq 48(%rsp), %rbx
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
movl %r14d, %esi
movl $_ZSt4cout, %edi
call _ZNSolsEi
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $8, %edx
movl $.LC1, %esi
movl $_ZSt4cout, %edi
call _ZSt16__ostream_insertIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_PKS3_l
pxor %xmm0, %xmm0
movl $_ZSt4cout, %edi
cvtss2sd 12(%rsp), %xmm0
call _ZNSo9_M_insertIdEERSoT_
movq %rax, %rdi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movl $.LC2, %esi
movl $_ZSt4cout, %edi
call _ZStlsISt11char_traitsIcEERSt13basic_ostreamIcT_ES5_PKc
movq %r13, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %r12, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %rbp, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movq %rbx, %rsi
movl $_ZSt4cout, %edi
call _ZNSo9_M_insertImEERSoT_
movl $_ZSt4cout, %edi
call _ZSt4endlIcSt11char_traitsIcEERSt13basic_ostreamIT_T0_ES6_
movq 72(%rsp), %rcx
xorq %fs:40, %rcx
jne .L5
addq $80, %rsp
.cfi_remember_state
.cfi_def_cfa_offset 48
xorl %eax, %eax
popq %rbx
.cfi_def_cfa_offset 40
popq %rbp
.cfi_def_cfa_offset 32
popq %r12
.cfi_def_cfa_offset 24
popq %r13
.cfi_def_cfa_offset 16
popq %r14
.cfi_def_cfa_offset 8
ret
.L5:
.cfi_restore_state
call __stack_chk_fail
.cfi_endproc
.LFE1495:
.size main, .-main
.section .text.unlikely
.LCOLDE3:
.section .text.startup
.LHOTE3:
.section .text.unlikely
.LCOLDB4:
.section .text.startup
.LHOTB4:
.p2align 4,,15
.type _GLOBAL__sub_I_main, @function
_GLOBAL__sub_I_main:
.LFB1689:
.cfi_startproc
subq $8, %rsp
.cfi_def_cfa_offset 16
movl $_ZStL8__ioinit, %edi
call _ZNSt8ios_base4InitC1Ev
movl $__dso_handle, %edx
movl $_ZStL8__ioinit, %esi
movl $_ZNSt8ios_base4InitD1Ev, %edi
addq $8, %rsp
.cfi_def_cfa_offset 8
jmp __cxa_atexit
.cfi_endproc
.LFE1689:
.size _GLOBAL__sub_I_main, .-_GLOBAL__sub_I_main
.section .text.unlikely
.LCOLDE4:
.section .text.startup
.LHOTE4:
.section .init_array,"aw"
.align 8
.quad _GLOBAL__sub_I_main
.local _ZStL8__ioinit
.comm _ZStL8__ioinit,1,1
.hidden __dso_handle
.ident "GCC: (Ubuntu 5.4.0-6ubuntu1~16.04.12) 5.4.0 20160609"
.section .note.GNU-stack,"",@progbits
And diff is:
19c19
< pushq %rbx
---
> pushq %r14
21c21,24
< .cfi_offset 3, -16
---
> .cfi_offset 14, -16
> pushq %r13
> .cfi_def_cfa_offset 24
> .cfi_offset 13, -24
22a26,31
> pushq %r12
> .cfi_def_cfa_offset 32
> .cfi_offset 12, -32
> pushq %rbp
> .cfi_def_cfa_offset 40
> .cfi_offset 6, -40
23a33,35
> pushq %rbx
> .cfi_def_cfa_offset 48
> .cfi_offset 3, -48
26,27c38,43
< .cfi_def_cfa_offset 96
< movl 16(%rsp), %ebx
---
> .cfi_def_cfa_offset 128
> movl 16(%rsp), %r14d
> movss 56(%rsp), %xmm1
> movss %xmm1, 12(%rsp)
> movq 24(%rsp), %r13
> movq 32(%rsp), %r12
30a47,48
> movq 40(%rsp), %rbp
> movq 48(%rsp), %rbx
32c50
< movl %ebx, %esi
---
> movl %r14d, %esi
37d54
< movss 56(%rsp), %xmm1
41d57
< movss %xmm1, 12(%rsp)
52c68
< movq 24(%rsp), %rsi
---
> movq %r13, %rsi
55c71
< movq 32(%rsp), %rsi
---
> movq %r12, %rsi
58c74
< movq 40(%rsp), %rsi
---
> movq %rbp, %rsi
61c77
< movq 48(%rsp), %rsi
---
> movq %rbx, %rsi
71c87
< .cfi_def_cfa_offset 16
---
> .cfi_def_cfa_offset 48
73a90,97
> .cfi_def_cfa_offset 40
> popq %rbp
> .cfi_def_cfa_offset 32
> popq %r12
> .cfi_def_cfa_offset 24
> popq %r13
> .cfi_def_cfa_offset 16
> popq %r14
Upvotes: 0
Views: 1123
Reputation: 131646
First thing's first: Your get_large_name_info()
makes a copy of the info
field. While, in practice, the copying may be optimized away, it's probably not what you want to do; rather, it's more common to return a constant lvalue reference (in your case, const Info&
Secondly, you wrote:
I called info is alias variable for
large_name.get_large_name_info()
that term is confusing. The typical use of the word "aliasing" w.r.t. C and C++ is for when mutlitple pointers (or perhaps references) point to overlapping areas in memory. if your method returned a const&
, then you could say you've got a sort of an alias for the member info
of class LargeName
.
Finally, why not use the field name as the getter name, instead?
This would give you:
class LargeName {
public:
// … many fields and methods
const Info& info() const { return info_; }
private:
Info info_;
}
and you would write, say:
void foo(LargeName bar) {
bar.info().d1;
bar.info().d2;
bar.info().d3;
bar.info().d4;
}
it's true that you can shorten this further, but: if you just a variable named info
in foo()
, people might not know which info
that is.
PS - Maybe your Info
class should have a size_t d[4] rather than four fields? That shortens some thing as well (at the cost of no distinct identifiers).
I can't reproduce this. With GCC 10.1, I get the same code for both scenarios in your appendix.
Upvotes: 1
Reputation: 17454
Your approach is logical, and clean, and neat.
As for performance, you can only really find this out by measuring it and reading the assembly on your particular machine with your particular build settings. But I'd be very surprised if you paid any "cost" here. Compilers are clever; they don't force you into a pointer dereference if they don't need to. Besides, references aren't just "pointers in disguise", they symbolise a new name for an existing piece of data, and your compiler understands what you're trying to do with that.
If anything, you may gain from skipping some unnecessary Info
copy constructions and calls to get_large_name_info()
.
Even if there were some small performance hit for some reason, unless you've put this code into a tight loop, I imagine the improved legibility of the code is well worth the cost of a tiny thing like a pointer dereference.
In fact, since you're copying the Info
, the whole reference bit is a completely unnecessary distraction; you're just extending the lifetime of a local temporary anyway. Much simpler, but just the same:
const Info info = large_name.get_large_name_info();
Now you don't even have a reference to worry about.
Finally, there doesn't seem to be any reason to return that Info
by value. Absent "optimisations" and the like, this is just logically unnecessary. If you return the original member as const Info&
instead, then the entire discussion is moot, and the compiler will still probably not form a pointer and some dereference operations, because it's clever and knows that it doesn't need to.
tl;dr: probably don't worry about it
Upvotes: 1