C++中变量混叠的最佳方法
Optimal way to variable aliasing in C++
我想学习C++中的变量别名,以便在不付出任何代价的情况下使代码比以前少很多(零成本),因为我可以更好地描述我的目的,请参阅以下详细信息
我有一个类似下面的结构
struct Info
{
size_t d1;
size_t d2;
size_t d3;
size_t d4;
};
我在另一个类中使用了这个结构。(称为LargeName
类)类似于
class LargeName
{
public:
// … many fields and methods
Info get_large_name_info() const
{
return info;
}
private:
Info info;
}
在这种情况下,如果我想在外部函数中使用LargeName
即时信息字段,我必须在下面这样做
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;
}
正如您在上面的例子中看到的,如果我的类名和信息的getter访问名太长,我必须编写非常长的字符才能使用d1到d4。但我想避开他们。例如,我可以写下面的代码来防止
void foo(LargeName large_name)
{
const Info& info = large_name.get_large_name_info();
info.d1;
info.d2;
info.d3;
info.d4;
}
正如你所看到的,我的代码比以前更干净了,也更短了。(我称info
是large_name.get_large_name_info()
的别名变量)但问题是,我不确定编译器是否能为新版本的代码生成与以前相同的代码。
我认为第二个foo实现是我使用info变量的付费参考和取消引用成本。
我有两个问题:
第一个问题是如何在不付出任何代价的情况下在C++中可变混叠
注意:两个foo
函数的汇编版本不同。我检查了带有-std=c++11 -O3 -S
开关的GCC 5.4.0版本。
第二个问题是为什么编译器为那些foo
方法生成不同的代码?在哪种情况下,这两个代码(第一个foo
和第二个foo
)的行为不完全相同(我想了解编译器为什么生成不同的代码?)?(我认为如果两个代码完全相同,那么生成的程序集必须相同)
--------------------附录------------------
一个完整的例子:
源代码:
#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;
}
用g++ -std=c++11 -O3 -D=SC1 -S main.cpp
编译
场景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
用g++ -std=c++11 -O3 -D=SC2 -S main.cpp
编译
场景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
差异是:
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
您的方法是合乎逻辑、干净整洁的。
至于性能,只有测量它并读取具有特定构建设置的特定机器上的程序集,才能真正发现这一点。但如果你在这里支付任何"费用",我会感到非常惊讶。编译者很聪明;如果不需要的话,它们不会强迫你去引用指针。此外,引用不仅仅是"伪装中的指针",它们象征着现有数据的新名称,你的编译器知道你想用它做什么。
如果有什么不同的话,您可以跳过一些不必要的Info
复制构造和对get_large_name_info()
的调用。
即使由于某种原因出现了一些小的性能问题,除非你把这段代码放在一个紧密的循环中,否则我认为代码的易读性提高是值得的,就像指针取消引用一样。
事实上,由于您正在复制Info
,因此整个引用位是完全不必要的干扰;无论如何,你只是在延长当地临时工的寿命。简单得多,但都一样:
const Info info = large_name.get_large_name_info();
现在你甚至连一个推荐人都不用担心了。
最后,似乎没有任何理由按值返回Info
。如果没有"优化"之类的东西,这在逻辑上是没有必要的。如果您将原始成员返回为const Info&
,那么整个讨论就没有意义了,编译器可能仍然不会形成指针和一些取消引用操作,因为它很聪明,并且知道不需要。
tl;博士:也许不用担心
"别名"和您的代码设计
首先:get_large_name_info()
对info
字段进行复制。虽然在实践中,复制可能会被优化掉,但这可能不是你想要做的;相反,更常见的是返回一个常量左值引用(在您的例子中,const Info&
其次,你写道:
我调用的info是
large_name.get_large_name_info()
的别名变量
这个词令人困惑。C和C++中"别名"一词的典型用法是指多个指针(或引用)指向内存中的重叠区域。如果您的方法返回了一个const&
,那么您可以说您已经为类LargeName
的成员info
获得了一种别名
最后,为什么不使用字段名作为getter名称呢?
这会给你:
class LargeName {
public:
// … many fields and methods
const Info& info() const { return info_; }
private:
Info info_;
}
你会写,说:
void foo(LargeName bar) {
bar.info().d1;
bar.info().d2;
bar.info().d3;
bar.info().d4;
}
的确,你可以进一步缩短这个时间,但是:如果你在foo()
中只使用一个名为info
的变量,人们可能不知道那是哪个info
。
PS-也许你的Info
类应该有一个size_td[4]而不是四个字段?这也缩短了一些事情(代价是没有明显的标识符)。
不同方法的所谓不同代码
我无法复制这个。对于GCC 10.1,我在您的附录中得到了两种场景的相同代码。
- 在C#中处理C++指针而不使用unsafe的最佳方法
- 在C++中,将大的无符号浮点数四舍五入为整数的最佳方法是什么
- 实现无开销push_back的最佳方法是什么
- 在C++中向零方向近似的最佳方法
- 使用不同的CRT将新的C++代码与旧的(二进制)组件隔离开来的最佳方法是什么
- 检测win32服务创建和删除的最佳方法
- 在C++中样板"冷/never_inline"错误处理技术的最佳方法是什么?
- 在 c++ 中对类中的 c 字符串动态数组进行排序的最佳方法是什么?
- 将线程中的数据存储到全局容器的最佳方法?
- 将一系列整数放入类的最佳方法是什么?
- 在派生类中使用基类的私有成员变量的最佳方法
- 在 C++ 中将非指定类型作为参数传递的最佳方法?
- Qt - QVector 和模型视图 - 从列表视图获取自定义类的最佳方法是什么?
- 使用 Git 处理 C++ Visual Studio 2019 解决方案的外部依赖项源代码管理的最佳方法是什么?
- 比较两个节点坐标的最佳方法是什么?
- 在nodejs中使用本机代码的最佳方法是什么?
- 将 pybind11 绑定标记为已弃用的最佳方法
- C++:将向量传递到构造函数以创建成员变量的最佳方法?
- C++中变量混叠的最佳方法
- 读取大文件(>2GB)(文本文件包含以太网数据)并通过不同参数随机访问数据的最佳方法是什么?