表达式 E 是核心常量表达式,除非对 E 的求值遵循抽象机 ([intro.execution]) 的规则,将求值以下其中一项:
- new 表达式 ([expr.new]),除非所选分配函数是可替换的全局分配函数 ([new.delete.single]、[new.delete.array]),并且分配的存储空间在E;
放置新表达式不是常量表达式。
为了解决这个问题,C++20 添加了std::construct_at. 那么为什么不能将placement-new表达式设为常量表达式呢?
以下类(带有虚拟析构函数)包含模板化的operator delete:
struct S\n{\n virtual ~S() {}\n template <typename... Args>\n void operator delete(void* ptr, Args... args);\n};\nRun Code Online (Sandbox Code Playgroud)\nargs可以为空,所以我认为S::operator delete也可以在预期常规时使用delete。
但是(使用 g++),我收到错误\xef\xbc\x9a
\n\n\n错误:“S”没有合适的“操作符删除”
\n
难道“合适的‘操作删除’”就不能成为一个模板吗?
\n有些函数应该是非抛出的,但标准对此没有任何说明。例如erase(q)(q表示有效的可解引用常量迭代器)的关联容器。根据[res.on.exception.handling#4],这允许实现抛出任何标准异常:
C++ 标准库中定义的函数没有 Throws: 段落,但具有潜在抛出异常规范,可能会抛出实现定义的异常。170 实现应通过抛出标准异常类或派生自标准异常类的异常来报告错误([坏.alloc]、[support.exception]、[std.exceptions])。
因此,如果您想吞掉它们抛出的任何实现定义的异常,则必须使用 try-catch 块。
std::set<int> s{1};
try
{
s.erase(s.cbegin());
}
catch (...) {}
Run Code Online (Sandbox Code Playgroud)
它丑陋且低效,但却是必要的。所以我也不知道这样做有什么好处。
我想知道这段代码是否合法:
delete new (operator new(1)) char;
Run Code Online (Sandbox Code Playgroud)
此代码执行相同的操作,但不在从放置 new 获得的指针上使用删除表达式:
void* p = operator new(1);
new (p) char;
delete static_cast<char*>(p);
Run Code Online (Sandbox Code Playgroud)
[expr.delete#2]的标准规则:
在单对象删除表达式中,delete 操作数的值可以是空指针值、由先前的非数组 new 表达式产生的指针值,或者指向由以下方法创建的对象的基类子对象的指针:如此新的表达方式。如果不是,则行为未定义。在数组删除表达式中,delete 操作数的值可以是空指针值或由前一个数组 new-expression 产生的指针值。68 如果不是,则行为未定义。
该标准并没有说 new-express 不包括放置 new。所以我认为按照标准,第一段代码应该是合法的,但第二段代码应该是非法的。我的想法正确吗?
为什么标准这么说?调用operator new并在其上构造一个对象,其作用与new表达式几乎相同,但在获得的指针上使用delete表达式不一定合法。
对于分配数组,标准似乎允许这样做:
delete[] new (operator new[](2)) char[2];
Run Code Online (Sandbox Code Playgroud)
但由于未指定的开销,它应该是非法的。
为什么标准允许这样做?
编辑:根据[intro.object]/13:
对名为operator new 或operator new[] 的函数的任何隐式或显式调用都会在返回的存储区域中隐式创建对象,并返回指向合适的已创建对象的指针。
所以我什至可以编写以下代码:
delete static_cast<char*>(operator new(1));
Run Code Online (Sandbox Code Playgroud)
它销毁隐式创建的char对象并释放内存,执行与上面示例相同的操作。但根据标准,它绝对是非法的,因为没有使用 new 表达式。
为什么标准不允许这样做?
c++ memory-management placement-new language-lawyer delete-operator
例如,赋值运算符std::slice_array:
void operator=(const valarray<T>&) const; //#1
void operator=(const T&) const; //#2
const slice_array& operator=(const slice_array&) const; //#3
Run Code Online (Sandbox Code Playgroud)
#1并#2返回void,但#3返回const slice_array&。
它禁止某些代码,例如:
std::valarray<int> va{1, 2, 3, 4, 5, 6};
va[std::slice(3, 2, 2)] = va[std::slice(0, 2, 2)] = va[0];
Run Code Online (Sandbox Code Playgroud)
为什么?
下面的代码:
#include <new>
#include <iostream>
#include <cstdlib>
struct alignas(32) S
{
S() {throw 1;}
void* operator new(std::size_t count, std::align_val_t al)
{
return ::operator new(count, al);
}
void operator delete(void* ptr, std::align_val_t al)
{
std::cerr << "aligned delete\n";
::operator delete(ptr, al);
}
};
int main()
{
try
{
S* ps = new S;
}
catch(...)
{
}
}
Run Code Online (Sandbox Code Playgroud)
将输出aligned delete.
当然这并没有什么问题。但是,在我添加模板删除后:
struct alignas(32) S
{
...
template <typename... Args>
void operator delete(void* ptr, Args... args)
{
std::cerr << "templated …Run Code Online (Sandbox Code Playgroud) 我用 g++ 12.2.1 编译了以下代码:
#include <iostream>
#include <ranges>
#include <vector>
#include <algorithm>
#include <iterator>
int main()
{
std::vector<int> vi;
std::ranges::copy(std::views::istream<int>(std::cin) | std::views::take(3), std::back_inserter(vi));
for (auto i : vi)
std::cout << i << ' ';
}
Run Code Online (Sandbox Code Playgroud)
输入:
1 2 3
4
Run Code Online (Sandbox Code Playgroud)
输出:1 2 3
为什么我必须输入 4 个数字而不是 3 个数字并丢弃最后一个数字?怎么解决?
考虑:
\n(long long){1};\nRun Code Online (Sandbox Code Playgroud)\nIt\xe2\x80\x99s 不是C 风格的转换表达式每个 [expr.cast]/1 的
\n\n\n表达式 (T) 强制转换表达式的结果为 T 类型。如果 T 是左值引用类型或对函数类型的右值引用,则结果是左值;如果 T 是对对象类型的右值引用,则结果是 xvalue;否则结果是纯右值。
\n
\n\n强制转换表达式:
\n\n
\n- 一元表达式
\n- ( type-id ) 强制转换表达式
\n
{1}不应该是强制转换表达式。
它也不是每个 [expr.type.conv]/1 的函数式转换表达式:
\n\n\n简单类型说明符或类型名说明符后跟带括号的可选表达式列表或大括号初始化列表(初始化程序),在给定初始化程序的情况下构造指定类型的值。如果该类型是推导类类型的占位符,则它将替换为本子条款其余部分的类模板推导重载决策所选择的函数的返回类型。否则,如果类型包含占位符类型,则将其替换为由占位符类型推导 ([dcl.type.auto.deduct]) 确定的类型。
\n
\n简单类型说明符:
\n\n
- 嵌套名称说明符 opt 类型名称
\n- 嵌套名称说明符模板简单模板 ID
\n- decl类型说明符
\n- 占位符类型说明符
\n- 嵌套名称说明符 opt 模板名称
\n- 字符
\n- char8_\xc2\xadt
\n- char16_\xc2\xadt
\n …
析构函数的语法是~classname. 这导致需要在析构函数调用中显式写入对象的类型。为了避免这种情况,C++17 引入了std::destroy_at.
那么,Bjarne Stroustrup 选择~classname析构函数语法的最初理由是什么?如果语法不依赖于类型,std::destroy_at则不需要。
c++ ×9
c++20 ×2
casting ×1
destructor ×1
exception ×1
grammar ×1
new-operator ×1
noexcept ×1
std-ranges ×1
valarray ×1