我想使用 CRTP 为定义某些函数组的所有类型编写一个通用接口。以下代码无法编译,因为我在定义其返回类型之前调用了一个函数:
// interface.h
struct Obj;
template <typename Derived>
struct Interface
{
// error: return type 'struct Obj' is incomplete
Obj f() const { return static_cast<const Derived&>(*this).f_impl(); }
};
// a.h
struct A : Interface<A>
{
Obj f_impl();
};
// obj.h
struct Obj{};
// a.cpp
#include "a.h"
#include "obj.h"
Obj A::f_impl()
{
return {};
}
Run Code Online (Sandbox Code Playgroud)
使用 C++17 和保证复制省略,我希望编译器在定义 f 时不再需要 Obj 的定义。这可能吗?还是必须在interface.h 中#include obj.h?第二个选项很烦人,因为它大大增加了编译时间。
考虑以下程序:
#include <functional>
#include <iostream>
class RvoObj {
public:
RvoObj(int x) : x_{x} {}
RvoObj(const RvoObj& obj) : x_{obj.x_} { std::cout << "copied\n"; }
RvoObj(RvoObj&& obj) : x_{obj.x_} { std::cout << "moved\n"; }
int x() const { return x_; }
void set_x(int x) { x_ = x; }
private:
int x_;
};
class Finally {
public:
Finally(std::function<void()> f) : f_{f} {}
~Finally() { f_(); }
private:
std::function<void()> f_;
};
RvoObj BuildRvoObj() {
RvoObj obj{3};
Finally run{[&obj]() { obj.set_x(5); }};
return …Run Code Online (Sandbox Code Playgroud) 我希望从这个测试程序中看到命名返回值优化 (NRVO) 的复制省略,但它的输出是“地址不匹配!” 所以 NRVO 没有发生。为什么是这样?
// test.cpp
// Compile using:
// g++ -Wall -std=c++17 -o test test.cpp
#include <string>
#include <iostream>
void *addr = NULL;
class A
{
public:
int i;
int j;
#if 0
~A() {}
#endif
};
A fn()
{
A fn_a;
addr = &fn_a;
return fn_a;
}
int main()
{
A a = fn();
if (addr == &a)
std::cout << "Addresses match!\n";
else
std::cout << "Addresses do not match!\n";
}
Run Code Online (Sandbox Code Playgroud)
笔记:
如果通过启用#if上述定义析构函数,则 …
我有以下代码。
#include <iostream>
struct Box {
Box() { std::cout << "constructed at " << this << '\n'; }
Box(Box const&) { puts("copy"); }
Box(Box &&) = delete;
~Box() { std::cout << "destructed at " << this << '\n'; }
};
auto f() {
Box v;
return v; // is it eligible for NVRO?
}
int main() {
auto v = f();
}
Run Code Online (Sandbox Code Playgroud)
上面的代码产生错误。 call to deleted constructor/function in both gcc and clang
但是,如果我更改代码以返回纯右值,则代码有效。
auto f() {
Box v;
return Box(); …Run Code Online (Sandbox Code Playgroud) 考虑这个类
class A {
public:
tracker tra;
A(tracker _t) : tra(_t) {}
};
Run Code Online (Sandbox Code Playgroud)
并通过调用它
A a {tracker()};
Run Code Online (Sandbox Code Playgroud)
创建的对象tracker()在被存储到之前永远不会被使用a.tra
为什么编译器不优化所有的复制结构?
。
跟踪器定义如下:
class tracker {
public:
void mark(const char* v) {
std::cout << v << ' ' << this << std::endl;
}
tracker() {
mark("con");
}
tracker(const tracker& o) {
mark("cpy");
}
tracker(tracker&& o) {
mark("mov");
}
~tracker() {
mark("des");
}
tracker& operator=(const tracker&) {
mark("=cp");
return *this;
}
tracker& operator=(tracker&&) {
mark("=mv");
return *this;
}
};
Run Code Online (Sandbox Code Playgroud) 我正在使用 C++ 17,并且我有一个相当大(二维)的数字数组,我试图在命名空间范围内初始化。该数组旨在成为一个预先计算的查找表,将在我的代码的许多部分中使用。它的定义看起来像这样:
using table_type = std::array<std::array<uint64_t, SOME_BIG_NUMBER>, SOME_OTHER_BIG_NUMBER>;
table_type MyTable = ...
Run Code Online (Sandbox Code Playgroud)
其中表的总大小 > 200,000 左右。现在,表中的所有值都可以在编译时得知,因此最初我继续执行以下操作:
// Header.h
constexpr table_type MyTable = []()
{
table_type table{};
// code to initialize table...
return table;
}();
Run Code Online (Sandbox Code Playgroud)
当 MSVC 拒绝编译这段代码时,我最初的担心变成了现实。该表太大而无法在编译时计算。我本可以修改设置并增加允许的最大步数,但我真的不想这样做。
// Header.h
const extern table_type MyTable;
Run Code Online (Sandbox Code Playgroud)
// Implementation.cpp
const table_type MyTable = []()
{
table_type table{};
// code to initialize table...
return table;
}();
Run Code Online (Sandbox Code Playgroud)
当我编码时,我有一种感觉这也行不通,我是对的。MSVC 警告我在 lambda 函数中使用了超过 2MB 的堆栈内存,尽管它编译了可执行文件,但由于堆栈被炸毁,它在启动时会立即崩溃。
// Header.h
// MyTable is no longer const
extern …Run Code Online (Sandbox Code Playgroud) 可能重复:
为什么析构函数只被调用一次?
鉴于下面的代码,我无法理解gcc中的输出.我希望创建和销毁两个对象,但只能看到对构造函数和析构函数的一次调用.这里发生了什么事?
#include <string>
#include <iostream>
struct Huge{
Huge() { std::cout << "Constructor" << std::endl; }
Huge(Huge const &r) { std::cout << "Copy Constructor" << std::endl; }
~Huge() { std::cout << "Destructor" << std::endl; }
};
Huge g() {
std::cout << "Entering g" << std::endl;
Huge temp;
std::cout << "Exiting g" << std::endl;
return temp;
}
int main(){
Huge h2(g());
std::cout << "Before leaving main" << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
g ++(4.4)中此代码的输出是
输入g
构造函数
退出g
在离开主要之前
析构函数
因此,我对移动语义的理解是,它们允许您覆盖用于临时值(rvalues)的函数,并避免可能昂贵的副本(通过将状态从未命名的临时值移动到命名的左值).
我的问题是为什么我们需要特殊的语义?为什么C++ 98编译器不能忽略这些副本,因为编译器确定给定表达式是左值还是左值?举个例子:
void func(const std::string& s) {
// Do something with s
}
int main() {
func(std::string("abc") + std::string("def"));
}
Run Code Online (Sandbox Code Playgroud)
即使没有C++ 11的移动语义,编译器仍应能够确定传递给它的表达式func()是否是rvalue,因此不需要临时对象的副本.那为什么要有这个区别呢?似乎移动语义的这种应用本质上是复制省略或其他类似编译器优化的变体.
作为另一个例子,为什么要打扰如下代码呢?
void func(const std::string& s) {
// Do something with lvalue string
}
void func(std::string&& s) {
// Do something with rvalue string
}
int main() {
std::string s("abc");
// Presumably calls func(const std::string&) overload
func(s);
// Presumably calls func(std::string&&) overload
func(std::string("abc") + std::string("def"));
}
Run Code Online (Sandbox Code Playgroud)
似乎const std::string&重载可以处理这两种情况:lvalues像往常一样,rvalues作为const引用(因为临时表达式按照定义是const类).由于编译器知道表达式何时是左值或右值,因此可以决定是否在rvalue的情况下忽略副本.
基本上,为什么移动语义被认为是特殊的,而不仅仅是一个可以由前C++ 11编译器执行的编译器优化?
请考虑以下代码:
#include <iostream>
struct Thing
{
Thing(void) {std::cout << __PRETTY_FUNCTION__ << std::endl;}
Thing(Thing const &) = delete;
Thing(Thing &&) = delete;
Thing & operator =(Thing const &) = delete;
Thing & operator =(Thing &&) = delete;
};
int main()
{
Thing thing{Thing{}};
}
Run Code Online (Sandbox Code Playgroud)
我希望Thing thing{Thing{}};声明意味着临时对象的建设Thing使用默认的构造函数和建设类thing的对象Thing使用仅仅用创建的临时对象作为参数转移构造类.我希望这个程序被认为是不正确的,因为它包含一个被删除的移动构造函数的调用,即使它可能被省略.标准的class.copy.elision部分似乎也要求这样做:
即使呼叫被省略,也必须可以访问所选的构造函数
通过简化值类别保证复制省略的措辞似乎也不允许.
然而gcc 7.2(以及clang 4也是如此,但VS2017 仍然不支持保证复制省略)将编译此代码,只需移动构造函数调用即可.
在这种情况下哪种行为是正确的?
我的问题不同,因为我可能"知道"复制省略.我正在学习复制初始化.但是,以下代码使我感到困惑,因为我已经使用-fno-elide-contructors -O0选项关闭了copy-elision .
#include <iostream>
using namespace std;
class test{
public :
test(int a_, int b_) : a{a_}, b{b_} {}
test(const test& other)
{
cout << "copy constructor" << endl;
}
test& operator=(const test& other)
{
cout << "copy assignment" << endl;
return *this;
}
test(test&& other)
{
cout << "move constructor" << endl;
}
test& operator=(test&& other)
{
cout <<"move assignment" << endl;
return *this;
}
private :
int a;
int b;
};
test show_elide_constructors()
{
return …Run Code Online (Sandbox Code Playgroud) c++ initialization implicit-conversion copy-initialization copy-elision
c++ ×10
copy-elision ×10
c++17 ×3
rvo ×2
c++11 ×1
c++14 ×1
c++20 ×1
class ×1
constructor ×1
destructor ×1
heap-memory ×1
nrvo ×1
raii ×1