lkr*_*sen 265 c++ constructor exception throw
我正在和一位同事讨论如何从构造函数中抛出异常,并且我想要一些反馈.
从设计的角度来看,从构造函数中抛出异常是否可以?
假设我在一个类中包装一个POSIX互斥锁,它看起来像这样:
class Mutex {
public:
Mutex() {
if (pthread_mutex_init(&mutex_, 0) != 0) {
throw MutexInitException();
}
}
~Mutex() {
pthread_mutex_destroy(&mutex_);
}
void lock() {
if (pthread_mutex_lock(&mutex_) != 0) {
throw MutexLockException();
}
}
void unlock() {
if (pthread_mutex_unlock(&mutex_) != 0) {
throw MutexUnlockException();
}
}
private:
pthread_mutex_t mutex_;
};
Run Code Online (Sandbox Code Playgroud)
我的问题是,这是标准的方法吗?因为如果pthread mutex_init
调用失败,则互斥对象不可用,因此抛出异常可确保不会创建互斥锁.
我是否应该为Mutex类创建一个成员函数init,并pthread mutex_init
在其中调用将返回基于返回的bool pthread mutex_init
?这样我就不必为这种低级对象使用异常.
Nav*_*een 250
是的,从失败的构造函数中抛出异常是执行此操作的标准方法.阅读此常见问题解答有关处理失败的构造函数以获取更多信息.使用init()方法也可以,但创建互斥对象的每个人都必须记住必须调用init().我认为这违反了RAII原则.
Fer*_*cio 97
如果从构造函数中抛出异常,请记住,如果需要在构造函数初始化列表中捕获该异常,则需要使用函数try/catch语法.
例如
func::func() : foo()
{
try {...}
catch (...) // will NOT catch exceptions thrown from foo constructor
{ ... }
}
Run Code Online (Sandbox Code Playgroud)
与
func::func()
try : foo() {...}
catch (...) // will catch exceptions thrown from foo constructor
{ ... }
Run Code Online (Sandbox Code Playgroud)
小智 34
抛出异常是处理构造函数失败的最佳方法.您应该特别避免半构造一个对象,然后依靠类的用户通过测试某种类型的标志变量来检测构造失败.
在一个相关的点上,你有几种不同的异常类型来处理互斥错误的事实让我有点担忧.继承是一个很好的工具,但它可以被过度使用.在这种情况下,我可能更喜欢单个MutexError异常,可能包含信息性错误消息.
小智 15
#include <iostream>
class bar
{
public:
bar()
{
std::cout << "bar() called" << std::endl;
}
~bar()
{
std::cout << "~bar() called" << std::endl;
}
};
class foo
{
public:
foo()
: b(new bar())
{
std::cout << "foo() called" << std::endl;
throw "throw something";
}
~foo()
{
delete b;
std::cout << "~foo() called" << std::endl;
}
private:
bar *b;
};
int main(void)
{
try {
std::cout << "heap: new foo" << std::endl;
foo *f = new foo();
} catch (const char *e) {
std::cout << "heap exception: " << e << std::endl;
}
try {
std::cout << "stack: foo" << std::endl;
foo f;
} catch (const char *e) {
std::cout << "stack exception: " << e << std::endl;
}
return 0;
}
Run Code Online (Sandbox Code Playgroud)
输出:
heap: new foo
bar() called
foo() called
heap exception: throw something
stack: foo
bar() called
foo() called
stack exception: throw something
Run Code Online (Sandbox Code Playgroud)
析构函数不会被调用,所以如果需要在构造函数中抛出异常,那么很多东西(例如清理?)要做.
Ric*_*den 14
从构造函数中抛出是可以的,但是你应该确保在main启动之后和完成之前构造你的对象:
class A
{
public:
A () {
throw int ();
}
};
A a; // Implementation defined behaviour if exception is thrown (15.3/13)
int main ()
{
try
{
// Exception for 'a' not caught here.
}
catch (int)
{
}
}
Run Code Online (Sandbox Code Playgroud)
唯一不会从构造函数抛出异常的情况是,如果您的项目有禁止使用异常的规则(例如,Google不喜欢异常)。在这种情况下,您不会希望在构造函数中使用异常而不是在其他任何地方,而是必须使用某种 init 方法。
添加到这里的所有答案中,我想提到一个非常具体的原因/场景,您可能希望更喜欢从类的Init
方法而不是从 Ctor抛出异常(当然,这是首选和更常见的方法)。
我会提前提到,这个例子(场景)假设你不std::unique_ptr
为你的类的指针数据成员使用“智能指针”(即- )。
所以关键是:如果你希望你的类的 Dtor 在你(在这种情况下)捕获你的Init()
方法抛出的异常之后调用它时“采取行动” - 你不能从 Ctor 抛出异常,导致对 Ctor 的 Dtor 调用不会在“半生不熟”的对象上调用。
请参阅以下示例以证明我的观点:
#include <iostream>
using namespace std;
class A
{
public:
A(int a)
: m_a(a)
{
cout << "A::A - setting m_a to:" << m_a << endl;
}
~A()
{
cout << "A::~A" << endl;
}
int m_a;
};
class B
{
public:
B(int b)
: m_b(b)
{
cout << "B::B - setting m_b to:" << m_b << endl;
}
~B()
{
cout << "B::~B" << endl;
}
int m_b;
};
class C
{
public:
C(int a, int b, const string& str)
: m_a(nullptr)
, m_b(nullptr)
, m_str(str)
{
m_a = new A(a);
cout << "C::C - setting m_a to a newly A object created on the heap (address):" << m_a << endl;
if (b == 0)
{
throw exception("sample exception to simulate situation where m_b was not fully initialized in class C ctor");
}
m_b = new B(b);
cout << "C::C - setting m_b to a newly B object created on the heap (address):" << m_b << endl;
}
~C()
{
delete m_a;
delete m_b;
cout << "C::~C" << endl;
}
A* m_a;
B* m_b;
string m_str;
};
class D
{
public:
D()
: m_a(nullptr)
, m_b(nullptr)
{
cout << "D::D" << endl;
}
void InitD(int a, int b)
{
cout << "D::InitD" << endl;
m_a = new A(a);
throw exception("sample exception to simulate situation where m_b was not fully initialized in class D Init() method");
m_b = new B(b);
}
~D()
{
delete m_a;
delete m_b;
cout << "D::~D" << endl;
}
A* m_a;
B* m_b;
};
void item10Usage()
{
cout << "item10Usage - start" << endl;
// 1) invoke a normal creation of a C object - on the stack
// Due to the fact that C's ctor throws an exception - its dtor
// won't be invoked when we leave this scope
{
try
{
C c(1, 0, "str1");
}
catch (const exception& e)
{
cout << "item10Usage - caught an exception when trying to create a C object on the stack:" << e.what() << endl;
}
}
// 2) same as in 1) for a heap based C object - the explicit call to
// C's dtor (delete pc) won't have any effect
C* pc = 0;
try
{
pc = new C(1, 0, "str2");
}
catch (const exception& e)
{
cout << "item10Usage - caught an exception while trying to create a new C object on the heap:" << e.what() << endl;
delete pc; // 2a)
}
// 3) Here, on the other hand, the call to delete pd will indeed
// invoke D's dtor
D* pd = new D();
try
{
pd->InitD(1,0);
}
catch (const exception& e)
{
cout << "item10Usage - caught an exception while trying to init a D object:" << e.what() << endl;
delete pd;
}
cout << "\n \n item10Usage - end" << endl;
}
int main(int argc, char** argv)
{
cout << "main - start" << endl;
item10Usage();
cout << "\n \n main - end" << endl;
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我会再次提到,这不是推荐的方法,只是想分享一个额外的观点。
此外,正如您从代码中的一些印刷品中看到的那样 - 它基于 Scott Meyers(第 1 版)出色的“更有效的 C++”中的第 10 项。