我已经检查了所有主要的编译器,sizeof(std::tuple<int, char, int, char>)
所有这些都是16。大概它们只是将元素按顺序放入元组,因此由于对齐而浪费了一些空间。
如果元组在内部像这样存储元素:int, int, char, char
,则其sizeof可能为12。
实现有可能执行此操作,还是该标准中的某些规则禁止这样做?
C++ 17补充说std::destroy_at
,但没有任何std::construct_at
对应物.这是为什么?难道不能像下面这样简单地实现吗?
template <typename T, typename... Args>
T* construct_at(void* addr, Args&&... args) {
return new (addr) T(std::forward<Args>(args)...);
}
Run Code Online (Sandbox Code Playgroud)
这样可以避免不完全自然的放置新语法:
auto ptr = construct_at<int>(buf, 1); // instead of 'auto ptr = new (buf) int(1);'
std::cout << *ptr;
std::destroy_at(ptr);
Run Code Online (Sandbox Code Playgroud) 考虑一个简单的程序:
int main() {
int* ptr = nullptr;
delete ptr;
}
Run Code Online (Sandbox Code Playgroud)
对于GCC(7.2),在结果程序中有call
关于的指令operator delete
.对于Clang和Intel编译器,没有这样的指令,空指针删除被完全优化(-O2
在所有情况下).你可以在这里测试:https://godbolt.org/g/JmdoJi.
我想知道这样的优化能否以某种方式与GCC一起开启?(我更广泛的动机源于自定义swap
vs std::swap
可移动类型的问题,其中删除空指针可能代表第二种情况下的性能损失; 有关详细信息,请参阅/sf/answers/3198249771/.)
UPDATE
为了澄清我对这个问题的动机:如果我在移动赋值运算符和某个类的析构函数中使用delete ptr;
无if (ptr)
保护,那么该类的对象将产生3个GCC指令.这可能是相当大的性能损失,例如,在对这些对象的数组进行排序时.std::swap
call
此外,我可以if (ptr) delete ptr;
在任何地方写作,但是想知道,这是否也不能成为性能损失,因为delete
表达式也需要检查ptr
.但是,在这里,我想,编译器只会生成一个检查.
此外,我真的很喜欢delete
没有后卫的可能性,这对我来说是一个惊喜,它可以产生不同的(表现)结果.
UPDATE
我只是做了一个简单的基准测试,即排序对象,它们delete
在移动赋值运算符和析构函数中调用.来源是:https://godbolt.org/g/7zGUvo
std::sort
使用GCC 7.1测量的运行时间和-O2
Xeon E2680v3上的标志:
链接代码中存在一个错误,它会比较指针,而不是指向值.更正结果如下:
if
后卫:我知道关于这个主题的多个问题,但是,我没有看到任何明确的答案或任何基准测量.因此,我创建了一个简单的程序,它使用两个整数数组.第一个数组a
非常大(64 MB),第二个数组b
很小,适合L1缓存.程序迭代a
并将其元素添加到b
模块化意义上的相应元素中(当到达结束时b
,程序从其开始再次开始).测量的不同大小的L1缓存未命中数b
如下:
测量是在具有32 kiB L1数据高速缓存的Xeon E5 2680v3 Haswell型CPU上进行的.因此,在所有情况下,都b
适合L1缓存.然而,大约16 kiB的b
内存占用量大大增加了未命中数.这可能因为两者的负载预期a
并b
导致缓存线失效从一开始b
在这一点上.
绝对没有理由保留a
缓存中的元素,它们只使用一次.因此,我运行一个具有非时间负载a
数据的程序变体,但未命中数没有改变.我还运行了一个非暂时预取a
数据的变体,但仍然有相同的结果.
我的基准代码如下(没有显示非时间预取的变体):
int main(int argc, char* argv[])
{
uint64_t* a;
const uint64_t a_bytes = 64 * 1024 * 1024;
const uint64_t a_count = a_bytes / sizeof(uint64_t);
posix_memalign((void**)(&a), 64, a_bytes);
uint64_t* b;
const uint64_t b_bytes = atol(argv[1]) * 1024;
const uint64_t b_count = b_bytes …
Run Code Online (Sandbox Code Playgroud) 在下面的C++代码中,我可以显式调用析构函数而不是构造函数.这是为什么?不明确的ctor调用与dtor案件更具表现力和统一性吗?
class X { };
int main() {
X* x = (X*)::operator new(sizeof(X));
new (x) X; // option #1: OK
x->X(); // option #2: ERROR
x->~X();
::operator delete(x);
}
Run Code Online (Sandbox Code Playgroud) c++ constructor destructor placement-new explicit-destructor-call
我刚刚发现,std::vector<T>::resize
即使调整大小超过当前大小的一个元素,它的容量"加倍":
std::vector<int> v(50);
v.resize(51);
std::cout << v.capacity() << std::endl;
Run Code Online (Sandbox Code Playgroud)
该程序使用GCC和Clang输出100,使用Visual C++输出75.但是,当我切换resize
到reserve
:
std::vector<int> v(50);
v.reserve(51);
std::cout << v.capacity() << std::endl;
Run Code Online (Sandbox Code Playgroud)
所有三个编译器的输出为51.
我不知道为什么实现使用了不同的扩张策略resize
和reserve
.它似乎不一致,我希望在这里有相同的行为.
我只是添加了一个关于我的问题动机的链接,其中报告了对性能的影响:为什么C++ STL向量在做多个预留时会慢1000倍?
添加C++ 11标准中的引用以阐明要求reserve
; §23.3.6.3(2):
之后
reserve()
,capacity()
为大于或等于给的参数reserve
,如果重新分配发生...
一些额外的想法:来自C++ 11标准:
复杂性:插入元素的数量加上到向量末尾的距离是复杂的.
实际上,这意味着在最后插入单个元素的常数(摊销)复杂性.但是,这仅适用于矢量修饰符,例如push_back
或insert
(§23.3.6.5).
resize
未在修饰符中列出.它列在§23.3.6.3 vector
容量部分中.并且,没有复杂性要求resize
.
但是,在vector
概述部分(§23.3.6.1)中,写有:
it(
vector
)支持(分期)常量时间插入和擦除操作
问题是是否resize(size()+1)
被认为是"插入到最后".
来自cppreference.com的报价:
添加模板特化
允许为任何标准库类添加模板特化(从C++ 20开始)| 模板到命名空间std只有当声明依赖于至少一个程序定义的类型并且特化满足原始模板的所有要求时,除非禁止这样的特化.
是否意味着,从C++ 20开始,将std
不再允许将函数模板的特化添加到用户定义类型的命名空间中?如果是这样,它意味着许多现有代码可能会破坏,不是吗?(在我看来,这是一种"激进"的改变.)此外,它会向这些代码注入未定义的行为,这不会触发编译错误(警告有希望).
我无法弄清楚为什么在最后一种情况下启用了复制省略时调用的移动构造函数(甚至是强制性的,例如在C++ 17中):
class X {
public:
X(int i) { std::clog << "converting\n"; }
X(const X &) { std::clog << "copy\n"; }
X(X &&) { std::clog << "move\n"; }
};
template <typename T>
X make_X(T&& arg) {
return X(std::forward<T>(arg));
}
int main() {
auto x1 = make_X(1); // 1x converting ctor invoked
auto x2 = X(X(1)); // 1x converting ctor invoked
auto x3 = make_X(X(1)); // 1x converting and 1x move ctor invoked
}
Run Code Online (Sandbox Code Playgroud)
在这种情况下,哪些规则阻碍了移动构造函数被省略?
UPDATE
调用移动构造函数时可能更直接的情况: …
c++ language-lawyer move-semantics copy-elision perfect-forwarding
请考虑以下头文件:
// Foo.h
class Foo {
public:
template <typename T>
void read(T& value);
};
Run Code Online (Sandbox Code Playgroud)
我想Foo::read
在源文件中显式实例化成员函数模板,包括在boost::mpl::vector
:
// Foo.cc
#include <boost/mpl/vector.hpp>
#include <boost/mpl/begin_end.hpp>
#include "Foo.h"
template <typename T>
void Foo::read(T& value) { /* do something */ }
typedef boost::mpl::vector<int, long, float> types;
// template Foo::read<int >(int&);
// template Foo::read<long >(long&);
// template Foo::read<float>(float&);
// instantiate automatically ???
Run Code Online (Sandbox Code Playgroud)
可能吗?谢谢,丹尼尔.
编辑
我找到了一些解决方案 - 似乎Foo::read<T>
在结构的构造函数中指定一个指针,然后声明该变量,导致实例化:
// intermezzo
template <typename T> struct Bar {
Bar<T>() {
void (Foo::*funPtr)(T&) = &Foo::read<T>; …
Run Code Online (Sandbox Code Playgroud) 对于库中的代码,是更好的做法是创建和抛出自定义异常类(库::例外),或只是抛出标准异常(runtime_error,invalid_argument等)?