我有一个函数,它产生一种昂贵的对象(包含向量和非固定大小的映射),所以我真的想避免调用copy c'tors.
到目前为止,我刚刚从方法中返回了一个std :: shared_ptr并使用了它,但我觉得它很难看,并且需要使用typedeffing才能真正使用它.
我知道有两件事可以帮助我.首先复制elision,第二个是移动语义.
我的问题是我知道如何正确使用.我的研究告诉我,复制省略完全由编译器完成,并不是st'd的一部分.我真的不想完全依赖于此.
那么我如何确保调用移动分配并确保它已到位以防止编译器复制elision.
ResultSet &&generateResults()
{
//ResultSet a(); :S
ResultSet a;
a.populat(...
//blah blah blah
return a;
}
//else where (where the && assignment operator is overloaded
ResultsSet b = generateResults();
Run Code Online (Sandbox Code Playgroud)
在这种情况下,这是最正确的编码方式吗?如果不是,我怎么能改进它.我很高兴使用C++ 0x only构造.
BTW:我的编译器是gcc 4.6
我push_back是一个vector像这样的临时对象,
vector<A> vec;
vec.push_back(A("abc"));
Run Code Online (Sandbox Code Playgroud)
将编译器应用copy-elision A("abc")直接构造临时vector,以便A在将临时对象推入时不会触发copy ctor vec.
我发现了这篇文章http://cpp-next.com/archive/2009/08/want-speed-pass-by-value/
作者的建议:
不要复制函数参数.相反,按值传递它们,让编译器进行复制.
但是,我在文章中提到的两个示例中没有获得什么好处:
//Don't
T& T::operator=(T const& x) // x is a reference to the source
{
T tmp(x); // copy construction of tmp does the hard work
swap(*this, tmp); // trade our resources for tmp's
return *this; // our (old) resources get destroyed with tmp
}
Run Code Online (Sandbox Code Playgroud)
VS
// DO
T& operator=(T x) // x is a copy of the source; hard work already done
{
swap(*this, x); // trade our resources for x's
return *this; // our …Run Code Online (Sandbox Code Playgroud) #include <iostream>
struct A
{
A() { std::cout << "Def Constr\n"; }
A(const A&) { std::cout << "Copy Constr\n"; }
};
A func1()
{
return A{};
}
void func2(A a) {}
int main()
{
func2(func1());
}
Run Code Online (Sandbox Code Playgroud)
编译完成后
g ++ Copy.cpp -std = c ++ 11 -fno-elide-constructors
输出是:
Def Constr
复制Constr
复制Constr
我的问题是:为什么2复制Constr?我以为只需要1份复印件.
我可能猜测func1()会抛出一个临时对象,并且需要将此临时对象复制到另一个内存区域,并且必须再次从该区域为func2()参数创建一个副本,但它对我来说是模糊的.
你能详细解释一下吗?
我试图使用c ++类型系统删除复制构造函数,以防止复制对象.
struct DeleteCopyConstructor {
DeleteCopyConstructor() {};
DeleteCopyConstructor(DeleteCopyConstructor& op2) = delete;
DeleteCopyConstructor(const DeleteCopyConstructor& op2) = delete;
};
DeleteCopyConstructor f() {
DeleteCopyConstructor d;
// initialize d...
return d;
}
Run Code Online (Sandbox Code Playgroud)
错误是:
error: use of deleted function ‘DeleteCopyConstructor::DeleteCopyConstructor(const DeleteCopyConstructor&)’
Run Code Online (Sandbox Code Playgroud)
我读过有关复制省略,但它似乎是一个编译器优化,所以我认为它不适用.如何在d不触发复制构造的情况下返回?
众所周知,std::move不应该将其应用于函数返回值,因为它可以阻止RVO(返回值优化).我感兴趣的是,如果我们确实知道RVO不会发生,我们应该怎么做.
这就是C++ 14标准所说的[12.8/32]
当满足复制/移动操作的省略标准时,但不满足异常声明,并且要复制的对象由左值指定,或者当返回语句中的表达式是(可能带有括号的)id-时表达式,用于在最内层封闭函数或lambda-expression的body或parameter-declaration-clause中声明的具有自动存储持续时间的对象,首先执行重载决策以选择复制的构造函数,就像对象由rvalue指定一样.如果第一个重载决策失败或未执行,或者所选构造函数的第一个参数的类型不是对象类型的rvalue引用(可能是cv-qualified),则再次执行重载决策,将对象视为左值.[注意:无论是否发生复制省略,都必须执行此两阶段重载决策.如果未执行elision,它将确定要调用的构造函数,并且即使调用被省略,也必须可以访问所选的构造函数. - 结束说明]
这是本书的解释 Effective Modern C++
标准祝福RVO的部分接着说,如果满足RVO的条件,但编译器选择不执行复制省略,则返回的对象必须被视为右值.实际上,标准要求在允许RVO时,发生复制省略或将std :: move隐式应用于返回的本地对象
据我所知,当返回物体最初不能被省略时,它应被视为rvalue.在这些例子中,我们可以看到,当我们传递大于5object的参数时,否则会被复制.std::move当我们知道RVO不会发生时,这是否意味着我们应该明确写出来?
#include <iostream>
#include <string>
struct Test
{
Test() {}
Test(const Test& other)
{
std::cout << "Test(const Test&)" << std::endl;
}
Test(Test&& other)
{
std::cout << "Test(const Test&&)" << std::endl;
}
};
Test foo(int param)
{
Test test1;
Test test2;
return param > 5 ? std::move(test1) : test2;
}
int main()
{
Test res = foo(2);
}
Run Code Online (Sandbox Code Playgroud)
这个程序的输出是Test(const …
c++ return-value move-semantics return-value-optimization copy-elision
我在探索丑陋的世界std::intializer_list.
据我所知,从标准:
§11.6.4:
- std :: initializer_list类型的对象是从初始化列表构造的,就好像实现生成并实现了(7.4)类型为"const const E"的prvalue,其中N是初始化列表中的元素数.使用初始化列表的相应元素对该数组的每个元素进行复制初始化,并构造std :: initializer_list对象以引用该数组.[注意: 在初始化列表的上下文中,可以访问为副本选择的构造函数或转换函数(第14条). - 尾注] [...]
因此,如果类型E是一个类,我希望调用复制构造函数.
以下类不允许复制构造:
struct NonCopyable {
NonCopyable() = default;
NonCopyable(const NonCopyable&) = delete;
};
Run Code Online (Sandbox Code Playgroud)
我将尝试std::initializer_list使用此类实例化一个.
#include <vector>
void foo() {
std::vector<NonCopyable>{NonCopyable{}, NonCopyable{}};
}
Run Code Online (Sandbox Code Playgroud)
随着g++-8.2 -std=c++14我得到我期望的,编译器错误:
error: use of deleted function 'NonCopyable::NonCopyable(const NonCopyable&)'.
完善!
但是,行为随新标准而变化.
的确,g++-8.2 -std=c++17编译.
我认为这是因为copy elision新标准提出的新要求,起初.
但是,更改标准库实现 …
这是我尝试使用代码执行的操作的描述,请跳到下一部分以查看实际问题。
我想在嵌入式系统中使用协程,但我无法承受太多的动态分配。因此,我正在尝试以下操作:对于对外围设备的各种查询,我有不可复制、不可移动的可等待类型。查询外围设备时,我使用类似auto result = co_await Awaitable{params}. 可等待的构造函数准备对外围设备的请求,注册其内部buffer以接收回复,并ready在承诺中注册其标志。然后协程被挂起。
稍后,buffer将被填充,并且ready标志将被设置为true。此后,协程知道它可以恢复,这会导致等待对象在被销毁之前从缓冲区复制出结果。
可等待的内容是不可复制且不可移动的,以强制在任何地方进行有保证的复制省略,这样我就可以确保指向buffer并ready保持有效的指针,直到等待可等待的内容为止(至少这是计划......)
我在以下代码中遇到 ARM GCC 11.3 的问题:
#include <cstring>
#include <coroutine>
struct AwaitableBase {
AwaitableBase() = default;
AwaitableBase(const AwaitableBase&) = delete;
AwaitableBase(AwaitableBase&&) = delete;
AwaitableBase& operator=(const AwaitableBase&) = delete;
AwaitableBase& operator=(AwaitableBase&&) = delete;
char buffer[65];
};
struct task {
struct promise_type
{
bool* ready_ptr;
task get_return_object() { return {}; }
std::suspend_never initial_suspend() noexcept { return …Run Code Online (Sandbox Code Playgroud) 我接受了一次采访,得到了以下函数声明:
int f1(const std::vector<int> vec)
Run Code Online (Sandbox Code Playgroud)
我建议我们不要复制向量,而应该使用 const 引用(更不用说 const 复制没有多大意义),但面试官声称编译器复制省略会处理它。我当场想不出什么强有力的论据,但今天我做了一些研究。
我实现了以下两个简单的示例函数:
int f1(const std::vector<int> vec) {
const auto num = vec.size();
return num * num;
}
int f2(const std::vector<int>& vec) {
const auto num = vec.size();
return num * num;
}
Run Code Online (Sandbox Code Playgroud)
从godbolt汇编中可以清楚地看出,该f2函数有 2 条附加指令,因此它应该更慢。(我认为 2mov在现代 CPU 中技术上几乎是免费的)
我还使用quick-bench来测量这两个解决方案,但它证实了我的怀疑,通过const-ref更快。(即使对于 1 个元素)
我怀疑可能因为 的原因而不允许复制省略benchmark::DoNotOptimize(result);,但删除它后,我收到了类似的结果。
现在我有了这些结果,但我觉得还是不够有说服力。
你怎么认为?
对于使用其中一种而不是另一种,您有什么好的理由吗?
c++ ×10
copy-elision ×10
c++11 ×2
c++17 ×1
class ×1
diagnostics ×1
oop ×1
optimization ×1
return-value ×1
vector ×1