请考虑以下代码:
#include <new>
#include <malloc.h>
#include <stdio.h>
void * operator new(size_t size) {
void *res;
if (size == 1) {
res = NULL;
} else {
res = malloc(size);
}
fprintf(stderr, "%s(%zu) = %p\n", __PRETTY_FUNCTION__, size, res);
if (res == NULL) throw std::bad_alloc();
return res;
}
void * operator new(size_t size, const std::nothrow_t&) {
void *res;
if (size == 1) {
res = NULL;
} else {
res = malloc(size);
}
fprintf(stderr, "%s(%zu) = %p\n", __PRETTY_FUNCTION__, size, res);
return res;
}
void operator delete(void *ptr) {
fprintf(stderr, "%s(%p)\n", __PRETTY_FUNCTION__, ptr);
free(ptr);
}
void operator delete(void *ptr, const std::nothrow_t&) {
fprintf(stderr, "%s(%p)\n", __PRETTY_FUNCTION__, ptr);
free(ptr);
}
class Foo { };
class Bar {
public:
Bar() : ptr(new Foo()) {
fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
}
Bar(const std::nothrow_t&) noexcept : ptr(new(std::nothrow) Foo()) {
fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
}
~Bar() noexcept {
delete ptr;
}
Foo *ptr;
};
class Baz {
public:
Baz() : ptr(new Foo()) {
fprintf(stderr, "%s: ptr = %p\n", __PRETTY_FUNCTION__, ptr);
}
~Baz() {
delete ptr;
}
Foo *ptr;
};
int main() {
Bar *bar = new(std::nothrow) Bar(std::nothrow_t());
if (bar != NULL) {
delete bar;
} else { fprintf(stderr, "bad alloc on Bar(std::nothrow_t())\n"); }
fprintf(stderr, "\n");
try {
bar = new(std::nothrow) Bar();
delete bar;
} catch (std::bad_alloc) { fprintf(stderr, "bad alloc on Bar()\n"); }
fprintf(stderr, "\n");
try {
Baz *baz = new Baz();
delete baz;
} catch (std::bad_alloc) { fprintf(stderr, "bad alloc on Baz()\n"); }
}
这会产生以下输出:
void* operator new(size_t, const std::nothrow_t&)(8) = 0x1fed010
void* operator new(size_t, const std::nothrow_t&)(1) = (nil)
Bar::Bar(const std::nothrow_t&): ptr = (nil)
void operator delete(void*)((nil))
void operator delete(void*)(0x1fed010)
void* operator new(size_t, const std::nothrow_t&)(8) = 0x1fed010
void* operator new(std::size_t)(1) = (nil)
void operator delete(void*, const std::nothrow_t&)(0x1fed010)
bad alloc on Bar()
void* operator new(std::size_t)(8) = 0x1fed010
void* operator new(std::size_t)(1) = (nil)
void operator delete(void*)(0x1fed010)
bad alloc on Baz()
正如您所看到的那样,尽管分配了Foo失败,但仍然可以成功分配第一个Bar。 Bar的第二次分配和Baz的alloaction通过使用std :: bad_alloc正确地失败。
现在我的问题是:如何制作“new(std :: nothrow)Bar(std :: nothrow_t());”释放Bar的内存并在Foo无法分配时返回NULL?依赖倒置是唯一的解决方案吗?
答案 0 :(得分:2)
C ++11§5.3.4/ 18:
“如果上述对象初始化的任何部分通过抛出异常和合适的终止而终止 可以找到deallocation函数,调用deallocation函数来释放对象所在的内存 正在构建,之后异常继续在 new-expression 的上下文中传播。
因此std::nothrow
不保证 new-expression 不会出现异常。它只是传递给分配函数的参数,从标准库中选择不抛出的参数。它显然主要是支持更多C风格的预标准代码。
现代C ++中的整个清理机制基于异常。
要解决这个问题 - 我认为这很愚蠢,不是一件可以做的事,但你要问的是 - 例如。
#include <iostream>
#include <new>
#include <stdexcept>
#include <stdlib.h> // EXIT_FAILURE
#include <typeinfo>
#include <utility>
namespace my { class Foo; }
template< class Type, class... Args >
auto null_or_new( Args&&... args )
-> Type*
{
#ifdef NULLIT
if( typeid( Type ) == typeid( my::Foo ) ) { return nullptr; }
#endif
try
{
return new( std::nothrow ) Type( std::forward<Args>( args )... );
}
catch( ... )
{
return nullptr;
}
}
namespace my
{
using namespace std;
class Foo {};
class Bah
{
private:
Foo* p_;
public:
Bah()
: p_( null_or_new<Foo>() )
{
clog << "Bah::<init>() reports: p_ = " << p_ << endl;
if( !p_ ) { throw std::runtime_error( "Bah::<init>()" ); }
}
};
} // namespace my
auto main() -> int
{
using namespace std;
try
{
auto p = null_or_new<my::Bah>();
cout << p << endl;
return EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
为什么请求的方法恕我直言愚蠢:
它放弃了例外的安全性。故障传播无法保证清理。确实无法保证故障传播,这一切都非常手动。
它会丢弃有关失败的所有信息,例如异常消息。人们可以添加机制来保留其中一些,但它会变得复杂和低效。
我认为没有合理的优势。
顺便说一下,请注意格式说明符%zu
和宏__PRETTY_FUNCTION__
不适用于Visual C ++。
另请注意,为了返回nullpointer,必须将分配函数声明为非抛出。
<强>附录强>
非常手动执行操作的示例,甚至避免内部异常。主要是成本是放弃通常的C ++机制,只有那些已成功构建的数据成员在检测到故障时才会被销毁。相反,所有内容都必须构建为虚拟状态,以便暂时可以使用僵尸对象。
#include <iostream>
#include <new>
#include <stdexcept>
#include <stdlib.h> // EXIT_FAILURE
#include <typeinfo>
#include <utility>
namespace my { class Foo; }
struct Result_code { enum Enum { success, failure }; };
template< class Type, class... Args >
auto null_or_new( Args&&... args )
-> Type*
{
#ifdef NULLIT
if( typeid( Type ) == typeid( my::Foo ) ) { return nullptr; }
#endif
auto code = Result_code::Enum();
auto const p = new( std::nothrow ) Type( code, std::forward<Args>( args )... );
if( p != nullptr && code != Result_code::success )
{
p->Type::~Type();
::operator delete( p, std::nothrow );
return nullptr;
}
return p;
}
namespace my
{
using namespace std;
class Foo { public: Foo( Result_code::Enum& ) {} };
class Bah
{
private:
Foo* p_;
public:
Bah( Result_code::Enum& code )
: p_( null_or_new<Foo>() )
{
clog << "Bah::<init>() reports: p_ = " << p_ << endl;
if( !p_ ) { code = Result_code::failure; }
}
};
} // namespace my
auto main() -> int
{
using namespace std;
try
{
auto p = null_or_new<my::Bah>();
cout << p << endl;
return EXIT_SUCCESS;
}
catch( exception const& x )
{
cerr << "!" << x.what() << endl;
}
return EXIT_FAILURE;
}
答案 1 :(得分:0)
假设您希望能够在没有例外的情况下进行失败的构造。
我将描绘这样一个系统。
template<class Sig>
struct has_creator;
template<class T, class...Args>
struct has_creator<T(Args...)>
如果您的类型true_type
具有与签名T
匹配的静态方法,则这是一个从bool T::emplace_create(T*, Args&&...)
下降的特征类。
emplace_create
在创建失败时返回false。 T*
必须指向一个未初始化的内存块,并且正确对齐并且sizeof(T)
或更大。
我们现在可以这样写:
template<class T, class...Args>
T* create( Args&&... args )
这是一个检测是否T
has_creator
的函数,如果是,则分配内存,执行emplace_create
,如果失败则清除内存并返回{{1} }。当然,它使用nothrow nullptr
。
现在,您可以使用new
代替create<T>
。
最大的缺点是我们不能很好地支持继承。组合变得棘手:我们基本上在new
中编写我们的构造函数,让我们的实际构造函数几乎没有,并且在emplace_create
中我们处理失败案例(比如调用失败emplace_create
的子对象)。
我们也接下来没有继承的帮助。如果我们需要有关继承的帮助,我们可以编写两种不同的方法 - 一种用于无故障初始构造,另一种用于容易出现故障的资源。
我会注意到,如果你停止在任何地方存储原始指针,它会变得不那么烦人。如果你将事物存储在create<X>
的任何地方(甚至到std::unique_ptr
返回create<T>
),并且使用中止投入一个守卫的末端驱逐舰,你的析构函数必须能够处理“半结构”物体。