小编Plu*_*uto的帖子

声明为 noexcept 的函数在其默认参数中引发异常时的奇怪行为

这是一个关于我的问题的例子:

struct B {
    B(B&&, int = (throw 0, 0)) noexcept {}
};
Run Code Online (Sandbox Code Playgroud)

我知道这是一段非常奇怪的代码。它只是用来说明问题。的移动构造函数B有一个noexcept说明符,而它有一个抛出异常的默认参数。

如果我使用noexcept运算符来测试移动构造函数,它将返回false. 但是,如果我提供第二个参数,它将返回“true”(在 GCC 和 Clang 上):

noexcept( B(std::declval<B>()) );    // false
noexcept( B(std::declval<B>(), 1) ); // true
Run Code Online (Sandbox Code Playgroud)

然后我添加了 class D,它继承自B但不提供移动构造函数。

struct D : public B { };
Run Code Online (Sandbox Code Playgroud)

我测试了类D

noexcept( D(std::declval<D>()) );  // true
Run Code Online (Sandbox Code Playgroud)

我已经阅读了标准,我认为根据标准,noexcept( D(std::declval<D>()) )应该返回false.

现在我试着按照标准来分析结果。

根据[expr.unary.noexcept]

noexcept运算符的结果是,true除非表达式可能抛出([except.spec])。

所以现在我们需要判断表达式是否B(std::declval<B>())是 …

c++ exception language-lawyer noexcept

16
推荐指数
1
解决办法
330
查看次数

为什么在《C++ Concurrency in Action》中的无锁队列实现中应用这些内存顺序?

简要描述;简介:

下面的代码是《C++ Concurrency in Action 第二版》中无锁队列的实现。队列采用链表作为底层数据结构,采用分割引用计数技术实现。

compare_exchange_strong让我困惑的是,代码中有多个使用acquire内存顺序,但我不明白为什么使用这个顺序。

例如,在对对象count的成员的所有访问中node(见下文),不存在有顺序的存储操作release,但有很多有顺序compare_exchange_strong的操作acquire

有关此无锁队列的完整代码、我对代码的解释以及我的问题的详细描述,请阅读完整描述部分。


详细描述:

我正在阅读 Anthony Williams 所著的《C++ Concurrency in Action》第二版。本书介绍了如何使用分割引用计数技术来实现无锁队列。我首先根据我的理解简单解释一下代码是如何工作的,以帮助你快速阅读代码。稍后将给出该队列的完整实现。

该实现使用单链表来实现队列。队列保存了指向链表头节点和尾节点的指针,分别是代码中的head和,并指向一个虚拟节点。tailtail

当将元素推入队列时,我们需要将元素值放入 指向的虚拟节点中tail,然后在该节点后面添加一个虚拟节点,并让tail指向新的虚拟节点。

当从队列中弹出一个元素时,我们应该弹出 指向的元素head,然后设置headhead->next。当headtail指向同一个节点(即哑节点)时,队列为空。

该代码使用引用计数来管理已删除节点的生命周期。与节点相关的引用计数分为两部分,外部引用计数和内部引用计数。外部计数加上内部计数就是该节点的完整引用计数值。外部计数存储在指向节点的指针中(即counted_node_ptr在代码中),而内部计数存储在节点对象内部(即count在代码中)。

为了防止一个线程所指向的对象在解引用指针之前被另一个线程删除,外部计数器必须首先递增以确保该节点不被删除。这是通过increase_external_count()代码完成的。

当外部引用不再引用某个节点时,其中存储的外部计数必须添加到该节点的内部计数中,这是由 完成的free_external_counter()。内部计数存储在count.internal_count节点对象中。并count.external_counters表示引用该节点的外部引用的数量。由于这些外部引用各自拥有自己的外部计数,因此必须将这些计数全部考虑在内。当且仅当内部计数为0并且外部计数器的数量也为0时,该节点才能被安全删除。

对于pop其中未成功指向正在弹出的节点的引用(通常是因为另一个线程已经弹出了该节点),这些引用将被简单地丢弃,但必须减去它们的引用计数。这是由 完成的release_ref()。该函数将节点的内部计数减一。每次引用计数递减,或者内部计数和外部计数合并时,都会检查是否满足删除节点的条件。

下面是书中无锁队列的完整实现:

template<typename T> …
Run Code Online (Sandbox Code Playgroud)

c++ concurrency multithreading atomic lock-free

9
推荐指数
1
解决办法
362
查看次数

为什么 GCC 分配的堆栈内存比需要的多?

我正在阅读“计算机系统:程序员的视角,3/E”(CS:APP3e),以下代码是书中的示例:

long call_proc() {
    long  x1 = 1;
    int   x2 = 2;
    short x3 = 3;
    char  x4 = 4;
    proc(x1, &x1, x2, &x2, x3, &x3, x4, &x4);
    return (x1+x2)*(x3-x4);
}
Run Code Online (Sandbox Code Playgroud)

书中给出了GCC生成的汇编代码:

long call_proc()
call_proc:
    ; Set up arguments to proc
    subq    $32, %rsp           ; Allocate 32-byte stack frame
    movq    $1, 24(%rsp)        ; Store 1 in &x1
    movl    $2, 20(%rsp)        ; Store 2 in &x2
    movw    $3, 18(%rsp)        ; Store 3 in &x3
    movb    $4, 17(%rsp)        ; Store 4 in …
Run Code Online (Sandbox Code Playgroud)

c gcc x86-64 compiler-optimization stack-memory

8
推荐指数
1
解决办法
678
查看次数

C++17中模板参数包的偏序规则

这是来自temp.deduct.partial的示例。

template<class... Args>           void f(Args... args);         // #1
template<class T1, class... Args> void f(T1 a1, Args... args);  // #2
template<class T1, class T2>      void f(T1 a1, T2 a2);         // #3

f();                // calls #1
f(1, 2, 3);         // calls #2
f(1, 2);            // calls #3; non-variadic template #3 is more specialized
                    // than the variadic templates #1 and #2
Run Code Online (Sandbox Code Playgroud)

为什么f(1, 2, 3)#2

CWG1395之前很容易理解。#2不能推导出,#1因为Args是一个参数包而T1不是,所以#2 …

c++ templates language-lawyer c++17

7
推荐指数
1
解决办法
95
查看次数

为什么编译器不能优化掉对此 const 参数的访问?

我已经在 Google 和 Stack Overflow 上搜索了一些答案,并且我知道编译器不能假设函数不会修改通过 const 引用传递的参数,因为这些函数可能会通过const_cast. 但是,当原始对象本身定义为 时,这样做是未定义的行为const。来自cpreference

通过非常量访问路径修改常量对象并通过非易失性左值引用易失性对象会导致未定义的行为。

对于以下代码

void fun(const int&);

int f1() {
    const int i = 3;
    fun(i);
    return i;
}

static int bar(const int i) {
    fun(i);
    return i;
}

int f2() {
    return bar(3);
}
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 都能够优化函数f1()以直接返回3,因为编译器认为调用fun(i)不会修改 的值i,因为这样的操作会导致未定义的行为。然而,GCC 和 Clang 都无法对该函数应用相同的优化f2()。编译器仍然生成代码以从内存加载值if1()下面是 GCC生成的代码f2()编译器资源管理器

f1():
        subq    $24, %rsp
        leaq    12(%rsp), …
Run Code Online (Sandbox Code Playgroud)

c++ optimization assembly gcc compiler-optimization

7
推荐指数
0
解决办法
174
查看次数

为什么MSVC STL使用这种方式让两个对象共享同一个缓存行?

以下是 MSVC STL 中的部分代码std::any

class any {
    // ...
    struct _Small_storage_t {
        unsigned char _Data[_Any_small_space_size];
        const _Any_small_RTTI* _RTTI;
    };
    static_assert(sizeof(_Small_storage_t) == _Any_trivial_space_size);

    struct _Big_storage_t {
        // Pad so that _Ptr and _RTTI might share _TypeData's cache line
        unsigned char _Padding[_Any_small_space_size - sizeof(void*)];
        void* _Ptr;
        const _Any_big_RTTI* _RTTI;
    };
    static_assert(sizeof(_Big_storage_t) == _Any_trivial_space_size);

    struct _Storage_t {
        union {
            unsigned char _TrivialData[_Any_trivial_space_size];
            _Small_storage_t _SmallStorage;
            _Big_storage_t _BigStorage;
        };
        uintptr_t _TypeData;
    };
    static_assert(sizeof(_Storage_t) == _Any_trivial_space_size + sizeof(void*));
    static_assert(is_standard_layout_v<_Storage_t>);

    union {
        _Storage_t _Storage{}; …
Run Code Online (Sandbox Code Playgroud)

c++ caching stl x86-64

6
推荐指数
0
解决办法
65
查看次数

为什么 [[no_unique_address]] 对公共数据成员没有影响?

考虑以下示例:

template <class T>
struct A {
    [[no_unique_address]] T t;
    int i;
};

struct B {
    long l;
    int i;
};

class C {
    long l;
    int i;
};
Run Code Online (Sandbox Code Playgroud)

GCC 和 Clang 都认为sizeof(A<B>)24sizeof(A<C>)而是16编译器资源管理器

类模板A<T>将属性T作为其数据成员之一[[no_unique_address]]B和之间的唯一区别CB是 astructC是 a class。我不明白为什么A<B>,而且A<C>尺寸不一样。换句话说,为什么编译器将i类模板的成员嵌入A<T>到 的尾部填充中C而不是嵌入到 的尾部填充中B

如果我修改B成员访问权限 …

c++ layout c++20

6
推荐指数
1
解决办法
119
查看次数

C++ Concurrency in Action 中危险指针的实现是否存在缺陷?

我正在阅读《C++ Concurrency in Action》第二版。下面的代码来自清单 7.6。它pop()使用危险指针来实现堆栈。

std::shared_ptr<T> pop() {
  std::atomic<void*>& hp = get_hazard_pointer_for_current_thread();
  node* old_head = head.load();  // #1
  do {
    node* temp;
    do {
      temp = old_head;
      hp.store(old_head);        // #2
      old_head = head.load();    // #3
    } while (old_head != temp);  // #4
  } while (old_head &&
           !head.compare_exchange_strong(old_head, old_head->next));
  hp.store(nullptr);
  // ...
}
Run Code Online (Sandbox Code Playgroud)

书中解释了内循环的作用:

您必须在while循环中执行此操作,以确保node在读取旧head指针#1和设置危险指针#2之间没有删除。在此窗口期间,没有其他线程知道您正在访问该特定节点。幸运的是,如果旧head节点要被删除,head那么它本身一定已经发生了变化,因此您可以检查这一点并继续循环,直到您知道该head指针仍然具有与您设置危险指针相同的值#4

根据 的实现,如果另一个线程在和之间pop删除了头节点,则将被修改为新节点。pop …

c++ concurrency multithreading atomic stdatomic

5
推荐指数
1
解决办法
434
查看次数

为什么可以使用标量类型作为operator-&gt;的返回类型?

来自cppreference

运算符的重载->必须返回原始指针,或者返回一个对象(通过引用或通过值),该对象->又重载运算符。

不过,我测试了以下示例,它被 GCC、Clang 和 MSVC 接受:

struct A {
  int operator->();
};
Run Code Online (Sandbox Code Playgroud)

根据 cppreference,返回类型int既不是指针也不是重载的类型operator->

为什么这个例子是正确的?标准中哪里描述了这种情况?标准允许这样做的目的是什么?

c++

1
推荐指数
1
解决办法
121
查看次数