小编Raz*_*ius的帖子

执行算术时是否也需要 std::launder ?

其中的先决条件上的std::launder要求,对象是其生命周期内。我认为这是能够取消引用元素的必要条件。这是否意味着,如果我获得指向数组元素的指针,那么如果我只执行算术运算,则不需要清洗?还是只是UB?

一个清晰的代码示例。在对代码的注释中做了一些澄清问题的注释。

alignas(T) std::byte memory[3 * sizeof(T)]; // implicitly creates an array of 3 T 
                                            // elements on the stack
auto arr_ptr = std::launder(reinterpret_cast<T(*)[3]>(memory));

// Now we have an implictly created array of 3 T, but unless T is an 
// implicit-lifetime type, no element has began its lifetime yet
auto ptr1 = reinterpet_cast<T*>(memory); // points to the storage occupied by the 
                                         // first element of T
// is arithmetic on ptr1 valid? Or …
Run Code Online (Sandbox Code Playgroud)

c++ arrays language-lawyer reinterpret-cast stdlaunder

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

数组和隐式生命周期对象创建

一些类型被标准定义为隐式生命周期类型,数组就在其中。一些函数隐式地创建具有隐式生命周期的对象(malloc 等就在其中),这里有一个隐式创建具有隐式生命周期的对象的操作列表。https://en.cppreference.com/w/cpp/language/object(我希望这是正确的,但对这个问题的其余部分我们假设new作品malloc在这种情况下隐含的对象创建的目的)。

如果数组不创建其元素,则隐式创建数组意味着什么?是不是意味着

T* implicit_array = reinterpret_cast<T*>(::operator new(sizeof(T) * count, std::align_val_t{alignof(T)}) );
Run Code Online (Sandbox Code Playgroud)

产生适用于指针算术的implicit_array 对象,即为稍后使用placement new 构造的类型T 的元素提供有效的存储?
这是否意味着这new (implicit_array + i) T{...}是一个定义明确的操作,即使按照标准,implicit_array + i不一定定义?https://eel.is/c++draft/expr.unary#op-3.2

或者这意味着

std::byte* memory = 
    reinterpret_cast<std::byte*>(::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)}));
new (memory) T{args1 ...}
// build more objects
new  (memory + (k-1)*sizeof(T) ) T{args_k ...} 
T* implicit_array = std::launder(reinterpret_cast<T*>(memory) ); // does it produce array of k elements?
Run Code Online (Sandbox Code Playgroud)

将其implicit_array视为具有 …

c++ arrays object-lifetime

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

简单可复制的类 - C++20 中发生了什么变化?

标准说

一个普通可复制的类是一个类:

(1.1) 至少有一个合格的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符([special]、[class.copy.ctor]、[class.copy.assign]),

(1.2) 其中每个合格的复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符都是微不足道的,并且

(1.3) 有一个简单的、未删除的析构函数 ([class.dtor])。

现在,我不完全确定这意味着什么。是否意味着拥有其中之一就足够了?例如,一个具有简单复制构造函数和复制赋值运算符以及显式删除的移动构造函数和移动赋值运算符的类是可以简单复制的,或者这意味着我有未删除的“big 6”并且它们中的每一个都是平凡的?

如果我从字面上理解这一点,那么只有一个构造函数或赋值运算符就足够了。根据cppreference在 c++20 之前就是这种情况。如果没有任何改变(即我仍然可以删除赋值运算符或构造函数),为什么措辞会改变?pre c++20 和 C++20 标准含义有什么区别?

更新

实验(例如Spencer 答案中的实验)表明我的猜测是正确的。我不明白的是 - 为什么要改变 C++20 标准中的措辞。实际上有什么改变吗?

c++17 定义的链接。

在 c++17 中,定义是:

  1. 一个普通可复制的类是一个类:

(6.1) 其中每个复制构造函数、移动构造函数、复制赋值运算符和移动赋值运算符 ([class.copy], [over.ass]) 要么被删除,要么被忽略,

(6.2) 具有至少一个非删除的复制构造函数、移动构造函数、复制赋值运算符或移动赋值运算符,并且

(6.3) 有一个简单的、未删除的析构函数。

旧定义和新定义之间是否存在细微差别?

c++ c++20 trivially-copyable

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

在 .net 5 中运行表单表单 F# 脚本?

是否可以将 Windows.Forms 称为 F# 脚本?正如我知道,当试图指他们.nu​​get包,说明有一个问题在这里,与潜在的解决方案(直接引用在目标机器上的文件),描述在这里。我没有尝试查看建议的解决方案是否适用于 .net core 3.1(帖子作者使用作者使用的)。但是,在直接引用光盘上的文件之后,我无法在 .net 5 下运行解决方案,错误类似于“无法加载文件或程序集 'System.Drawing.Common, Version=5.0.0.0, Culture = 中性,PublicKeyToken=cc7b13ffcd2ddd51'。无法找到或加载特定文件。(0x80131621)"。

这是代码:

#r "DotnetLocation/dotnet/shared/Microsoft.WindowsDesktop.App/5.0.1/System.Windows.Forms"
#r "DotnetLocation/dotnet/shared/Microsoft.WindowsDesktop.App/5.0.1/System.Drawing"
#r "DotnetLocation/dotnet/shared/Microsoft.WindowsDesktop.App/5.0.1/System.Drawing.Common"
#r "D:/F# Examples/Chapter7Libs/Vector/bin/Debug/netcoreapp5.0/Chapter7libs"
open System.Drawing
open System.Windows.Forms
Application.EnableVisualStyles()let winSize = Size(450, 300)
let display (title: string, (c: Curve.Curve, pw: int, ph: int)) =
   let f(x,y) = Point(int(round x), ph - int(round y))
   let clst = Curve.toList c
   let ptLst = List.map f clst
   let pArr = Array.ofList ptLst    let pen = new Pen(Color.Black) …
Run Code Online (Sandbox Code Playgroud)

f# f#-interactive .net-5

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

是否可以以不会导致 UB 的方式分配未初始化的数组?

在 C++ 中实现某些数据结构时,需要能够创建一个包含未初始化元素的数组。正因为如此,拥有

buffer = new T[capacity];
Run Code Online (Sandbox Code Playgroud)

不适合,因为new T[capacity]初始化数组元素,这并不总是可能的(如果 T 没有默认构造函数)或期望的(因为构造对象可能需要时间)。典型的解决方案是分配内存并使用placement new。

为此,如果我们知道元素的数量是已知的(或者至少我们有一个上限)并在堆栈上分配,那么,据我所知,可以使用对齐的字节或字符数组,然后使用std::launder访问成员。

alignas(T) std::byte buffer[capacity];
Run Code Online (Sandbox Code Playgroud)

但是,它只解决了栈分配的问题,并没有解决堆分配的问题。为此,我假设需要使用对齐新,并写这样的东西:

auto memory =  ::operator new(sizeof(T) * capacity, std::align_val_t{alignof(T)});
Run Code Online (Sandbox Code Playgroud)

然后将其转换为std::byte*orunsigned char*T*

// not sure what the right type for reinterpret cast should be
buffer = reinterpret_cast(memory);
Run Code Online (Sandbox Code Playgroud)

但是,有几件事我不清楚。

  1. reinterpret_cast<T*>(ptr)如果 ptr 指向可与 T 进行指针互转换的对象,则定义结果。(请参阅此答案https://eel.is/c++draft/basic.types#basic.compound-3)了解更多详细信息。我假设,将其转换T*为无效,因为 T 不一定与 new 的结果是指针可互转换的。但是,它是否为char*或定义明确std::byte
  2. 当将 的结果转换new为有效的指针类型(假设它不是实现定义的)时,它是否被视为指向数组第一个元素的指针,或者只是指向单个对象的指针?虽然,据我所知,在实践中很少(如果有的话)很重要,但存在语义差异,pointer_type + integer …

c++ memory-management void-pointers dynamic-memory-allocation reinterpret-cast

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

如何定义一个函数来处理移动语义和复制语义?

假设我正在实现一个集合并且我想向它添加一个元素,例如。

template <typename T>
class MyCollection
{
    void add(const T& element);
};
Run Code Online (Sandbox Code Playgroud)

现在,由于添加元素通常意味着复制它,出于效率原因,使用以下版本的 add 也是有意义的void add(T&& element)。现在的问题是,显然这两个函数的代码是完全一样的,只是参数类型不同。目前我对 C++ 的掌握是有限的,但我想知道是否有一种简单而惯用的方法来编写add一次函数而无需重写两次?

c++ move-semantics

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

内存分配和构造函数

很抱歉,如果在标准中明确说明之前已经询问过,但我无法找到自动存储的对象的内存是在封闭块的开头还是在执行构造函数之前分配?

我问这个是因为https://en.cppreference.com/w/cpp/language/storage_duration说的。

存储期 程序中的所有对象都具有以下存储期之一:

自动存储时间。对象的存储在封闭代码块的开头分配,并在结尾释放。所有本地对象都有这个存储持续时间,除了那些声明为 static、extern 或 thread_local 的对象。

现在,这是否意味着即使由于某种原因没有调用构造函数,也会分配存储空间?

例如,我有这样的事情。

{
     if(somecondition1) throw something;
     MyHugeObject o{};
     /// do something
}
Run Code Online (Sandbox Code Playgroud)

因此,有可能不需要构造 MyHugeObject,但根据我引用的来源,尽管该对象可能永远不会被构造,但仍为其分配了内存。是这种情况还是基于实现的东西?

c++ memory-management allocation

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