为什么这段代码有效:
template<
typename T,
std::enable_if_t<std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}
template<
typename T,
std::enable_if_t<!std::is_same<T, int>::value, T>* = nullptr>
void Add(T) {}
Run Code Online (Sandbox Code Playgroud)
并且可以正确区分这两个调用:
Add(1);
Add(1.0);
Run Code Online (Sandbox Code Playgroud)
而以下代码如果编译导致重新定义Add()错误?
template<
typename T,
typename = typename std::enable_if<std::is_same<T, int>::value, T>::type>
void Add(T) {}
template<
typename T,
typename = typename std::enable_if<!std::is_same<T, int>::value, T>::type>
void Add(T) {}
Run Code Online (Sandbox Code Playgroud)
所以如果模板参数是类型,那么我们就重新定义了函数,如果它是非类型的,那么一切都好.
我想实现一个函数调用的包装器,它会做这样的事情:
template <template<class> class F>
void Wrapper(int n, F&& f)
{
switch (n)
{
case 1:
f<std::int8_t>();
break;
case 2:
f<std::int16_t>();
break;
default:
break;
}
}
template <class T>
void func()
{
// ... body of func()
}
Run Code Online (Sandbox Code Playgroud)
这样我就可以在代码中进行以下调用:
Wrapper(1, func);
Run Code Online (Sandbox Code Playgroud)
但是上面的代码没有编译,因为F&& f构造是无效的 - 我需要指定参数的具体类型.但是,如果我使函数签名如下:
template <template<class> class F, class T>
void Wrapper(int n, F<T>&& f)
Run Code Online (Sandbox Code Playgroud)
然后我必须使用以下具体类型进行调用f:
Wrapper(1, func<std::int8_t>);
Run Code Online (Sandbox Code Playgroud)
我将无法切换Wrapper.
我该如何实现我需要的行为?
我有以下代码:
class Base
{
private:
class NestedBase
{
public:
void Do() {}
};
public:
NestedBase nested;
};
int main()
{
Base b;
b.nested.Do(); // line A compiles
Base::NestedBase instance; // line B doesn't compile
}
Run Code Online (Sandbox Code Playgroud)
NestedBaseclass是一个私有的嵌套类Base,所以B行无法编译似乎很自然.但是,另一方面,变量b有公共成员nested,我可以Do()从外部调用它的方法Base(如在A行中).在这种情况下,规范对私有嵌套类(或其成员)的访问的准确规则是什么?标准对此有何评价?
一方面,维基百科写了乱序执行的步骤:
- 取指令。
- 指令分派到指令队列(也称为指令缓冲区或保留站)。
- 指令在队列中等待,直到其输入操作数可用。然后允许该指令在较早、较旧的指令之前离开队列。
- 指令被发布到适当的功能单元并由该单元执行。
- 结果在排队。
- 只有在所有较旧的指令将其结果写回寄存器文件后,才会将该结果写回寄存器文件。这称为毕业或退休阶段。
类似的信息可以在《计算机组织与设计》一书中找到:
为了让程序表现得像在一个简单的有序流水线上运行,指令获取和解码单元需要按顺序发出指令,这允许跟踪依赖关系,并且提交单元需要将结果写入寄存器和程序获取顺序中的内存。这种保守的模式被称为有序提交……今天,所有动态调度的管道都使用有序提交。
因此,据我所知,即使指令以乱序方式执行,其执行结果也会保存在重新排序缓冲区中,然后以确定性顺序提交到内存/寄存器。
另一方面,有一个众所周知的事实,即现代 CPU 可以为性能加速目的重新排序内存操作(例如,可以重新排序两个相邻的独立加载指令)。维基百科在这里写到。
您能否解释一下这种差异?
cpu cpu-architecture dynamic-execution instructions pipelining
假设我有以下代码段:
template <class T>
class Bar
{
// static_assert(sizeof(T) > 0); // (1)
public:
void method()
{
static_assert(sizeof(T) > 0); // (2)
}
};
class Foo; // (3)
template class Bar<Foo>; // (4)
class Foo{}; // (5)
Run Code Online (Sandbox Code Playgroud)
如果我们取消注释第(1)行,我们得到一个编译时错误"不完整的类型T",它似乎很清楚:class Bar实例化由(4)启动,并且在那时class Foo只是由(3)向前声明尚未由(5)定义.
但是如果第(1)行被注释掉,那么这段代码没有错误编译,它让我困惑:(4)是一个显式的模板实例化定义,它强制编译器生成void method()代码,而line(2)也应该生成同样的错误,因为定义Foo是在后面的(5)中做出的.
我想念什么,为什么代码片段中的代码会编译?
更新:代码在GCC 8.2.0和MSVC 19.16.27025.1下编译,但在Clang 7.0.0下,它给出了"不完整类型"错误.
1)如果我没有弄错,C++标准保证单个翻译单元中的静态变量按其定义顺序初始化.我对以下代码片段感到困惑:
extern int n;
int k = n;
int n = 2;
Run Code Online (Sandbox Code Playgroud)
extern int n;是声明,而不是定义,所以k是之前定义的n,但是GCC,Clang和MSVC都告诉我k == 2全局变量初始化之后.对我来说,目前还不清楚如何k在之后分配2 int k = n;,因为n尚未在该点初始化,其值必须为零.
如果我们将最后一行更改为:
int n = func();
Run Code Online (Sandbox Code Playgroud)
哪里func()是非constexpr,然后k将被指定为零,正如我所期望的那样.那么,在编译时初始化全局变量会改变初始化的顺序吗?
2)这是另一个代码片段:
class Base
{
public:
struct static_constructor
{
static_constructor()
{
i = 1;
}
};
static static_constructor constructor;
static int i;
};
Base::static_constructor Base::constructor;
int Base::i = 2;
Run Code Online (Sandbox Code Playgroud)
当Base::constructor被定义,它的构造函数被调用,并i = 1进行分配.但是Base::i目前还没有定义,所以,请你解释一下此时发生了什么,为什么最后 …
c++ initialization global-variables static-variables static-members
如果std::condition_variable由于虚假的唤醒可以发出信号(我们无法确定我们需要的条件是否真的满足),为什么C++标准库提供wait()没有谓词的方法的重载?可以使用此类行为的情况是什么?
以下代码片段仅在我T为ctor 中的Base结构明确指定模板参数时才编译Derived:
template <class T>
struct Base
{
Base(int) {}
};
template <class T>
struct Derived : Base<T>
{
Derived(int i) : Base<T>(i) {}
};
Run Code Online (Sandbox Code Playgroud)
如果我调用Base(i)而不是Base<T>(i)- 它不起作用。为什么编译器不能确定它Base实际上是Base<T>(因为我来自Base<T>)?这个要求是故意的吗?
众所周知,由于使用了写缓冲区,x86 架构没有实现顺序一致性内存模型,因此可以进行 store->load 重新排序(可以提交稍后的加载,而较早的存储仍然驻留在写缓冲区中等待提交) L1缓存)。
在A Primer on Memory Consistency and Coherence 中,我们可以了解 Total Store Order(TSO) 内存一致性模型中的 Read-Modify-Write(RMW) 操作(应该与 x86 非常相似):
...我们将 RMW 视为紧随其后的负载。由于 TSO 的排序规则,RMW 的负载部分无法通过较早的负载。乍一看,RMW 的加载部分可能会传递写入缓冲区中较早的存储,但这是不合法的。如果 RMW 的加载部分通过较早的存储,则 RMW 的存储部分也必须通过较早的存储,因为 RMW 是原子对。但是由于TSO中不允许存储相互传递,因此RMW的负载部分也不能通过较早的存储。
好的,原子操作必须是原子的,即RMW访问的内存位置在RMW操作期间不能被其他线程/内核访问,但是如果较早的存储通过原子操作的加载部分,则与RMW 访问的内存位置?假设我们有以下几条指令(伪代码):
store int32 value in 0x00000000 location
atomic increment int32 value in 0x10000000 location
Run Code Online (Sandbox Code Playgroud)
第一个存储被添加到写缓冲区并等待轮到它。同时,原子操作从另一个位置(甚至在另一个缓存行中)加载值,传递第一个存储,然后将存储添加到第一个之后的写入缓冲区中。在全局内存顺序中,我们将看到以下顺序:
加载(原子的一部分)-> 存储(序数)-> 存储(原子的一部分)
是的,从性能的角度来看,这可能不是最好的解决方案,因为我们需要将原子操作的缓存行保持在读写状态,直到写入缓冲区中的所有先前存储都被提交,但是,抛开性能考虑,是是否存在违反 TSO 内存一致性模型的情况,我们是否允许 RMW 操作的加载部分将较早的存储传递到不相关的位置?
实现无锁数据结构的典型方法是使用原子 CAS 操作,例如std::compare_exchange_strong或std::compare_exchange_weak。这种技术的使用示例可以在 Antony Williams 的“C++ Concurrency in Action”中看到,其中实现了无锁堆栈。堆栈被实现为带有std::atomic<node*>头指针的链表。CAS 操作在推送和弹出期间在此指针上执行。但是 C++ 标准保证只有无std::atomic_flag锁,其他原子类型,包括std::atomic<T*>,可能不是无锁的。
1)我是否正确理解,如果std::atomic<T*>不是无锁(std::atomic::is_lock_free()返回false),那么基于CAS操作的数据结构std::atomic<T*>不是无锁的?
2)如果是,那么,如果std::atomic_flag是某些编译器的唯一无锁原子类型,那么在 C++ 上实现无锁数据结构的替代方法是什么?
假设我有一个简单的C#控制台应用程序:
class Program
{
static async void func()
{
Thread.CurrentThread.Name = "main";
await Task.Run(() =>
{
Thread.CurrentThread.Name = "child";
Thread.Sleep(5000);
});
Console.WriteLine("continuation is running on {0} thread", Thread.CurrentThread.Name);
}
static void Main(string[] args)
{
func();
Thread.Sleep(10000);
}
}
Run Code Online (Sandbox Code Playgroud)
当5000毫秒通过时,我们看到"继续在子线程上运行"消息.当另一个5000毫秒通过时,主线程完成其工作并关闭应用程序.它看起来很合乎逻辑:异步任务及其延续在同一子线程上运行.
但现在假设我有一个简单的WPF应用程序:
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}
async private void mainWnd_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
Thread.CurrentThread.Name = "main";
await Task.Run(() =>
{
Thread.CurrentThread.Name = "child";
Thread.Sleep(5000);
});
this.Title = string.Format("continuation is …Run Code Online (Sandbox Code Playgroud) 完美转发问题的常见描述表明我们最好不要使用&和const&作为包装函数参数的组合,因为在这种情况下,我们不得不编写覆盖函数参数的所有组合的多个函数:
template <typename T1, typename T2>
void wrapper(T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(T1& e1, const T2& e2) { func(e1, e2); }
template <typename T1, typename T2>
void wrapper(const T1& e1, const T2& e2) { func(e1, e2); }
Run Code Online (Sandbox Code Playgroud)
以下是该问题的经典解决方案:
template <typename T1, typename T2>
void wrapper(T1&& e1, T2&& e2) {
func(forward<T1>(e1), forward<T2>(e2));
} …Run Code Online (Sandbox Code Playgroud) 我有以下程序:
#include <iostream>
void Init();
struct Foo {
Foo() {
int *p = new int; // just to make sure Foo's ctor is not a constant expression
Init();
}
} foo;
struct Bar {
constexpr Bar()
: value(0) { }
int value;
} bar;
void Init() {
bar.value = 1;
}
int main()
{
std::cout << bar.value << std::endl;
}
Run Code Online (Sandbox Code Playgroud)
这里foo的构造函数不是常量表达式,因此我们将动态初始化foo。但是bar的构造函数似乎是一个常量表达式,因此我们将对进行静态初始化bar。因此,bar必须先调用的ctor,然后foo将其1视为输出。我在GCC 8.3.0和Clang 8.0.0中观察到了这样的结果。但是对于Visual C ++,实际的输出是,0 …
c++ static-variables visual-c++ static-initialization constexpr
c++ ×10
c++11 ×4
templates ×4
atomic ×2
async-await ×1
asynchronous ×1
c# ×1
const-cast ×1
constexpr ×1
cpu ×1
inheritance ×1
instructions ×1
lock-free ×1
memory-model ×1
overloading ×1
pipelining ×1
sfinae ×1
task ×1
template-argument-deduction ×1
visual-c++ ×1
wpf ×1
x86 ×1