小编jac*_*bsa的帖子

考虑到复制构造的要求,如何在C++ 11中编写有状态分配器?

据我所知,对于与STL容器一起使用的分配器的要求在C++ 11标准的第17.6.3.5节的表28中列出.

我对其中一些要求之间的相互作用感到有些困惑.给定一个类型X是类型分配器T,一类Y是"相应的allocator类"的类型U,实例a,a1a2X,和实例bY,表说:

  1. 表达式仅在分配的存储可以被解除分配时a1 == a2评估,反之亦然.truea1a2

  2. 表达式X a1(a);格式正确,不会通过异常退出,之后a1 == a也是如此.

  3. 表达式X a(b)格式正确,不会通过异常退出,然后退出a == b.

我读到这一点时说,所有分配器必须是可复制构造的,使得副本可以与原件互换.更糟糕的是,跨类型边界也是如此.这似乎是一个非常繁重的要求; 据我所知,它使大量类型的分配器变得不可能.

例如,假设我有一个我想在我的分配器中使用的freelist类,以便缓存释放的对象.除非我遗漏了某些东西,否则我无法在分配器中包含该类的实例,因为大小或对齐的TU可能不同,因此freelist条目不兼容.

我的问题:

  1. 我的解释是否正确?

  2. 我在一些地方读到C++ 11改进了对"有状态分配器"的支持.考虑到这些限制,情况如何?

  3. 你有什么建议可以做我想做的事吗?也就是说,如何在分配器中包含特定于分配类型的状态?

  4. 一般来说,分配器周围的语言似乎很草率.(例如,表28的序言假设它a是类型X&,但某些表达式重新定义a.)此外,至少GCC的支持是不符合的.分配器周围的这种奇怪的原因是什么?它只是一个不经常使用的功能吗?

c++ stl allocator c++11

34
推荐指数
3
解决办法
8913
查看次数

为什么 std::is_copy_constructible_v<std::vector<MoveOnlyType>> 为 true?

在我的 clang 和 libc++ 版本(near HEAD)中,这static_assert通过了:

static_assert(std::is_copy_constructible_v<std::vector<std::unique_ptr<int>>>)
Run Code Online (Sandbox Code Playgroud)

当然,如果您实际上尝试复制构造唯一指针的向量,它将无法编译:

../include/c++/v1/__memory/allocator.h:151:28: error: call to implicitly-deleted copy constructor of 'std::unique_ptr<int>'
        ::new ((void*)__p) _Up(_VSTD::forward<_Args>(__args)...);
[...]
note: in instantiation of member function 'std::vector<std::unique_ptr<int>>::vector' requested here
    const std::vector<std::unique_ptr<int>> bar(foo);
                                            ^
../include/c++/v1/__memory/unique_ptr.h:215:3: note: copy constructor is implicitly deleted because 'unique_ptr<int>' has a user-declared move constructor
  unique_ptr(unique_ptr&& __u) _NOEXCEPT
Run Code Online (Sandbox Code Playgroud)

我认为这种情况是因为实现在不可复制构造std::vector<T>时不使用 SFINAE 来禁用复制构造函数。T但为什么不呢?标准中是否有规定必须以这种方式工作?这是不幸的,因为这意味着我自己的围绕复制构造性的 SFINAE 并没有在向量方面做正确的事情。

c++ type-traits language-lawyer libc++ c++17

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

C++标准:命名空间范围的constexpr变量是否具有内部链接?

想象一下,我们有一个foo.h包含以下内容的标题:

#ifndef FOO_H_
#define FOO_H_

namespace foo {
constexpr std::string_view kSomeString = "blah";
}

#endif  // FOO_H_
Run Code Online (Sandbox Code Playgroud)

foo::kSomeString保证在任何翻译单元,包括内部联动foo.h?这在C++ 11和C++ 17之间有所不同吗?

在草案标准[basic.link]/3中

具有命名空间作用域的名称具有内部链接,如果它是非易失性const限定类型的非内联变量的名称,该变量既未显式声明为extern,也未声明为具有外部链接[...]

但我不知道是否constexpr算作"const-qualified".标准是否在某处说出来?

假设这保证具有内部链接,看起来ODR对此用法没有问题,对吧?(与此答案中的内容形成鲜明对比.)

c++ linkage language-lawyer c++11 c++17

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

C++ 标准的哪一部分阻止显式指定此模板的参数?

在我的C ++旅行我遇到下面的习惯(例如这里的绕绳下降),确保一个模板函数不能有模板参数显式指定的,所以它们并非保证API的一部分,并可以自由更改,恕不打破任何人:

template <int&... ExplicitArgumentBarrier, typename T>
void AcceptSomeReference(const T&);
Run Code Online (Sandbox Code Playgroud)

它似乎确实有效:

foo.cc:5:3: error: no matching function for call to 'AcceptSomeReference'
  AcceptSomeReference<char>('a');
  ^~~~~~~~~~~~~~~~~~~~~~~~~
foo.cc:2:6: note: candidate template ignored: invalid explicitly-specified argument for template parameter 'ExplicitArgumentBarrier'
Run Code Online (Sandbox Code Playgroud)

我在直观的层面上理解为什么会这样,但我想知道如何使它精确。标准的哪一部分保证无法为该模板显式指定模板参数?

我对 clang 在这里出色的错误信息感到惊讶;就像它识别成语一样。

c++ language-lawyer variadic-templates c++17 c++20

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

如何在避免未定义行为的同时将任意双精度转换为整数?

假设我有一个接受64位整数的函数,我想用一个double带有任意数值的函数调用它(即它的幅度非常大,甚至无限大):

void DoSomething(int64_t x);

double d = [...];
DoSomething(d);
Run Code Online (Sandbox Code Playgroud)

C++ 11标准中[conv.fpint]的第1段说明了这一点:

可以将浮点类型的prvalue转换为整数类型的prvalue.转换转发; 也就是说,丢弃小数部分.如果截断的值无法在目标类型中表示,则行为未定义.

因此,d上面有许多值会导致未定义的行为.我希望转换为饱和,因此大于std::numeric_limits<int64_t>::max()(kint64max在下面称为)的值 (包括无穷大)将成为该值,并且与最小可表示值类似.这似乎是一种自然的方法:

double clamped = std::min(d, static_cast<double>(kint64max));
clamped = std::max(clamped, static_cast<double>(kint64min));
DoSomething(clamped);
Run Code Online (Sandbox Code Playgroud)

但是,标准的下一段说明了这一点:

可以将整数类型或无范围枚举类型的prvalue转换为浮点类型的prvalue.如果可能,结果是准确的.如果要转换的值在可以表示的值范围内,但该值无法准确表示,则它是实现定义的下一个较低或较高可表示值的选择.

所以clamped可能仍然存在kint64max + 1,行为可能仍未定义.

什么是最简单的便携式方式来做我正在寻找的东西?奖励积分,如果它也优雅地处理NaNs.

更新:更确切地说,我希望以下内容对于int64_t SafeCast(double)解决此问题的 函数都是如此:

  1. 对于任何double d,调用SafeCast(d)不会根据标准执行未定义的行为,也不会抛出异常或以其他方式中止.

  2. 对于d范围内的任何双倍[-2^63, 2^63), SafeCast(d) == static_cast<int64_t>(d).也就是说,SafeCast在定义后者的任何地方都同意C++的转换规则.

  3. 任何双d >= 2^63,SafeCast(d) == kint64max.

  4. 任何双d < …

c++ type-conversion undefined-behavior language-lawyer

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

C++标准:可以将轻松的原子商店提升到互斥锁之上吗?

标准中是否有任何措辞可以保证放松存储到原子的位置不会超过锁定互斥锁?如果没有,是否有任何措辞明确表示编译器或CPU是否可以这样做?

例如,采取以下计划:

std::mutex mu;
int foo = 0;  // Guarded by mu
std::atomic<bool> foo_has_been_set{false};

void SetFoo() {
  mu.lock();
  foo = 1;
  foo_has_been_set.store(true, std::memory_order_relaxed);
  mu.unlock();
}

void CheckFoo() {
  if (foo_has_been_set.load(std::memory_order_relaxed)) {
    mu.lock();
    assert(foo == 1);
    mu.unlock();
  }
}
Run Code Online (Sandbox Code Playgroud)

CheckFoo如果另一个线程同时调用SetFoo,是否有可能在上述程序中崩溃,或者是否有一些保证不能将存储foo_has_been_set提升到mu.lock编译器和CPU 的调用之上?

这与一个较旧的问题有关,但我并不是100%明白,答案适用于此.特别是,该问题的答案中的反例可能适用于两个并发调用SetFoo,但我对编译器知道有一个调用SetFoo和一个调用的情况感兴趣CheckFoo.这是否保证安全?

我正在寻找标准中的具体引用.谢谢!

c++ atomic memory-model

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

为什么 std::is_invocable_r 拒绝返回不可移动类型的函数?

我很好奇的定义std::is_invocable_r以及它如何与不可移动类型交互。根据我对它应该模拟的语言规则的理解,它在 C++20 模式下 clang 下的 libc++ 实现似乎是错误的,所以我想知道我的理解有什么不正确。

\n

假设我们有一个不能移动或复制构造的类型,以及一个返回它的函数:

\n
struct CantMove {\n  CantMove() = default;\n  CantMove(CantMove&&) = delete;\n};\n\nstatic_assert(!std::is_move_constructible_v<CantMove>);\nstatic_assert(!std::is_copy_constructible_v<CantMove>);\n\nCantMove MakeCantMove() { return CantMove(); }\n
Run Code Online (Sandbox Code Playgroud)\n

然后可以调用该函数来初始化对象CantMove(我相信由于复制省略规则):

\n
CantMove cant_move = MakeCantMove();\n
Run Code Online (Sandbox Code Playgroud)\n

并且类型特征同意该函数是可调用的并返回CantMove

\n
using F = decltype(MakeCantMove);\nstatic_assert(std::is_invocable_v<F>);\nstatic_assert(std::is_same_v<CantMove, std::invoke_result_t<F>>);\n
Run Code Online (Sandbox Code Playgroud)\n

std::is_invocable_r表示不可能调用它来产生可转换为 的东西CantMove,至少在 C++20 下 clang 中是这样:

\n
static_assert(!std::is_invocable_r_v<CantMove, F>);\n
Run Code Online (Sandbox Code Playgroud)\n
\n

的定义是std::is_invocable_r

\n
\n

当被视为未计算的操作数时,该表达式INVOKE<R>(declval<Fn>(), declval<ArgTypes>()...)是格式良好的

\n
\n

INVOKE<R>定义为

\n
\n …

c++ sfinae type-traits language-lawyer c++20

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

"存储缓冲区转发"在英特尔开发人员手册中的含义是什么?

英特尔64和IA-32架构软件开发人员手册说,大约由单一处理器的行动("在P6更多最近的处理器系列内存排序和"第8.2.2节)重新排序如下:

读取可以使用较旧的写入到不同位置进行重新排序,但不能使用较旧的写入到同一位置.

接下来讨论与早期处理器相比放松的点时,它说:

存储缓冲区转发,当读取将写入传递到同一存储器位置时.

据我所知,"存储缓冲区转发"并未在任何地方精确定义(也不是"通过").读取将写入传递到同一位置是什么意思,因为上面说它不能通过写入同一位置来重新排序?

concurrency assembly intel cpu-architecture memory-model

12
推荐指数
2
解决办法
1965
查看次数

如何优雅地初始化std :: atomic数组?

假设我有一个带有std::atomics 成员数组的类,其中数组通过计算来调整大小(即它可能会根据程序中其他地方的其他常量而改变):

class Foo {
  static constexpr size_t kArraySize = ComputeArraySize();
  std::atomic<size_t> atomics_[kArraySize];
};
Run Code Online (Sandbox Code Playgroud)

确保原子全部初始化为零的最优雅方法是什么?我可以做的比在Foo构造函数中循环数组并显式存储零更好吗?答案std::array有何不同?

通常我会在这里使用大括号初始值设定项,但派生的长度(可能很长)会让它变得困难.

请注意,我不能假设该实例Foo具有静态存储持续时间.

c++ c++11

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

为什么没有operator << for std :: unique_ptr?

C++ 11标准中的[util.smartptr.shared.io]强制operator<<要求 shared_ptrs:

template<class E, class T, class Y>
basic_ostream<E, T>& operator<< (basic_ostream<E, T>& os, shared_ptr<Y> const& p);
Run Code Online (Sandbox Code Playgroud)

但是,除非我错过它,否则我在[unique.ptr]中看不到类似内容,en.cppreference.com上的引用同意.差异有原因吗?

c++ unique-ptr language-lawyer c++11

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