C++ 从构造函数抛出异常
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/810839/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Throwing exceptions from constructors
提问by lkristjansen
I'm having a debate with a co-worker about throwing exceptions from constructors, and thought I would like some feedback.
我正在与一位同事就从构造函数抛出异常进行辩论,并认为我需要一些反馈。
Is it OK to throw exceptions from constructors, from a design point of view?
从设计的角度来看,从构造函数抛出异常是否可以?
Lets say I'm wrapping a POSIX mutex in a class, it would look something like this:
假设我在一个类中包装了一个 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_;
};
My question is, is this the standard way to do it? Because if the pthread mutex_init
call fails the mutex object is unusable so throwing an exception ensures that the mutex won't be created.
我的问题是,这是标准的做法吗?因为如果pthread mutex_init
调用失败,则互斥对象将无法使用,因此抛出异常可确保不会创建互斥对象。
Should I rather create a member function init for the Mutex class and call pthread mutex_init
within which would return a bool based on pthread mutex_init
's return? This way I don't have to use exceptions for such a low level object.
我应该为 Mutex 类创建一个成员函数 init 并pthread mutex_init
在其中调用将基于pthread mutex_init
的返回返回一个 bool吗?这样我就不必为这样一个低级对象使用异常。
采纳答案by Naveen
Yes, throwing an exception from the failed constructor is the standard way of doing this. Read this FAQ about Handling a constructor that failsfor more information. Having a init() method will also work, but everybody who creates the object of mutex has to remember that init() has to be called. I feel it goes against the RAIIprinciple.
是的,从失败的构造函数抛出异常是执行此操作的标准方法。阅读有关处理失败的构造函数的常见问题解答以获取更多信息。使用 init() 方法也可以,但是创建互斥对象的每个人都必须记住必须调用 init()。我觉得这违背了RAII原则。
回答by Ferruccio
If you do throw an exception from a constructor, keep in mind that you need to use the function try/catch syntax if you need to catch that exception in a constructor initializer list.
如果确实从构造函数抛出异常,请记住,如果需要在构造函数初始值设定项列表中捕获该异常,则需要使用函数 try/catch 语法。
e.g.
例如
func::func() : foo()
{
try {...}
catch (...) // will NOT catch exceptions thrown from foo constructor
{ ... }
}
vs.
对比
func::func()
try : foo() {...}
catch (...) // will catch exceptions thrown from foo constructor
{ ... }
回答by Ferruccio
Throwing an exception is the best way of dealing with constructor failure. You should particularly avoid half-constructing an object and then relying on users of your class to detect construction failure by testing flag variables of some sort.
抛出异常是处理构造函数失败的最佳方式。你应该特别避免半构造一个对象,然后依靠你的类的用户通过测试某种标志变量来检测构造失败。
On a related point, the fact that you have several different exception types for dealing with mutex errors worries me slightly. Inheritance is a great tool, but it can be over-used. In this case I would probably prefer a single MutexError exception, possibly containing an informative error message.
在相关的一点上,您有几种不同的异常类型来处理互斥错误的事实让我有点担心。继承是一个很好的工具,但它可能会被过度使用。在这种情况下,我可能更喜欢单个 MutexError 异常,可能包含信息性错误消息。
回答by Xiaofeng
#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;
}
the output:
输出:
heap: new foo
bar() called
foo() called
heap exception: throw something
stack: foo
bar() called
foo() called
stack exception: throw something
the destructors are not called, so if a exception need to be thrown in a constructor, a lot of stuff(e.g. clean up?) to do.
析构函数没有被调用,所以如果需要在构造函数中抛出异常,需要做很多事情(例如清理?)。
回答by Richard Corden
It is OK to throw from your constructor, but you should make sure that your object is constructed after mainhas started and before it finishes:
从构造函数中抛出是可以的,但是你应该确保你的对象是在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)
{
}
}
回答by Michael Kohne
The only time you would NOT throw exceptions from constructors is if your project has a rule against using exceptions (for instance, Googledoesn't like exceptions). In that case, you wouldn't want to use exceptions in your constructor any more than anywhere else, and you'd have to have an init method of some sort instead.
唯一不会从构造函数抛出异常的情况是,如果您的项目有禁止使用异常的规则(例如,Google不喜欢异常)。在这种情况下,您将不希望在构造函数中使用异常而不是在其他任何地方,并且您必须使用某种 init 方法来代替。
回答by Guy Avraham
Adding to all the answers here, I thought to mention, a very specific reason/scenario where you might want to prefer to throw the exception from the class's Init
method and not from the Ctor (which off course is the preferred and more common approach).
在这里添加所有答案,我想提到一个非常具体的原因/场景,您可能希望从类的Init
方法而不是从 Ctor抛出异常(当然,这是首选和更常见的方法)。
I will mention in advance that this example (scenario) assumes that you don't use "smart pointers" (i.e.- std::unique_ptr
) for your class'
s pointer(s) data members.
我会提前提到这个例子(场景)假设你不使用“智能指针”(即- std::unique_ptr
)作为你类的指针数据成员。
So to the point: In case, you wish that the Dtor of your class will "take action" when you invoke it after (for this case) you catch the exception that your Init()
method threw - you MUST not throw the exception from the Ctor, cause a Dtor invocation for Ctor's are NOT invoked on "half-baked" objects.
所以关键是:如果你希望你的类的 Dtor 在你(对于这种情况)捕获你的Init()
方法抛出的异常之后调用它时“采取行动” - 你不能从 Ctor 抛出异常,导致对 Ctor 的 Dtor 调用不会在“半生不熟”的对象上调用。
See the below example to demonstrate my point:
请参阅以下示例以证明我的观点:
#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;
}
I will mention again, that it is not the recommended approach, just wanted to share an additional point of view.
我会再次提到,这不是推荐的方法,只是想分享一个额外的观点。
Also, as you might have seen from some of the print in the code - it is based on item 10 in the fantastic "More effective C++" by Scott Meyers (1st edition).
此外,正如您可能从代码中的一些印刷品中看到的那样 - 它基于 Scott Meyers(第 1 版)出色的“更有效的 C++”中的第 10 项。
回答by Zoran Horvat
If your project generally relies on exceptions to distinguish bad data from good data, then throwing an exception from the constructor is better solution than not throwing. If exception is not thrown, then object is initialized in a zombie state. Such object needs to expose a flag which says whether the object is correct or not. Something like this:
如果您的项目通常依赖异常来区分坏数据和好数据,那么从构造函数抛出异常比不抛出更好的解决方案。如果没有抛出异常,则对象被初始化为僵尸状态。这样的对象需要公开一个标志,说明对象是否正确。像这样的东西:
class Scaler
{
public:
Scaler(double factor)
{
if (factor == 0)
{
_state = 0;
}
else
{
_state = 1;
_factor = factor;
}
}
double ScaleMe(double value)
{
if (!_state)
throw "Invalid object state.";
return value / _factor;
}
int IsValid()
{
return _status;
}
private:
double _factor;
int _state;
}
Problem with this approach is on the caller side. Every user of the class would have to do an if before actually using the object. This is a call for bugs - there's nothing simpler than forgetting to test a condition before continuing.
这种方法的问题在于调用方。该类的每个用户都必须在实际使用该对象之前执行 if。这是对错误的呼吁 - 没有什么比在继续之前忘记测试条件更简单的了。
In case of throwing an exception from the constructor, entity which constructs the object is supposed to take care of problems immediately. Object consumers down the stream are free to assume that object is 100% operational from the mere fact that they obtained it.
如果构造函数抛出异常,构造对象的实体应该立即处理问题。下游的对象消费者可以自由地假设对象是 100% 可操作的,仅仅因为他们获得了它。
This discussion can continue in many directions.
这种讨论可以在许多方向上继续进行。
For example, using exceptions as a matter of validation is a bad practice. One way to do it is a Try pattern in conjunction with factory class. If you're already using factories, then write two methods:
例如,使用异常作为验证问题是一种不好的做法。一种方法是将 Try 模式与工厂类结合使用。如果你已经在使用工厂,那么写两个方法:
class ScalerFactory
{
public:
Scaler CreateScaler(double factor) { ... }
int TryCreateScaler(double factor, Scaler **scaler) { ... };
}
With this solution you can obtain the status flag in-place, as a return value of the factory method, without ever entering the constructor with bad data.
使用此解决方案,您可以就地获取状态标志,作为工厂方法的返回值,而无需输入带有错误数据的构造函数。
Second thing is if you are covering the code with automated tests. In that case every piece of code which uses object which does not throw exceptions would have to be covered with one additional test - whether it acts correctly when IsValid() method returns false. This explains quite well that initializing objects in zombie state is a bad idea.
第二件事是,如果您使用自动化测试覆盖代码。在这种情况下,使用不抛出异常的对象的每一段代码都必须进行一个额外的测试——当 IsValid() 方法返回 false 时它是否正确运行。这很好地解释了在僵尸状态下初始化对象是一个坏主意。
回答by g24l
Apart from the fact that you do not need to throwfrom the constructor in your specific case because pthread_mutex_lock
actually returns an EINVALif your mutex has not been initializedand you can throw after the call to lock
as is done in std::mutex
:
除了在您的特定情况下您不需要从构造函数抛出这一事实之外,因为如果您的互斥锁尚未初始化,则pthread_mutex_lock
实际上返回一个EINVAL并且您可以在调用后抛出,lock
如下所示std::mutex
:
void
lock()
{
int __e = __gthread_mutex_lock(&_M_mutex);
// EINVAL, EAGAIN, EBUSY, EINVAL, EDEADLK(may)
if (__e)
__throw_system_error(__e);
}
then in general throwing from constructors is okfor acquisitionerrors during construction, and in compliance with RAII( Resource-acquisition-is-Initialization ) programming paradigm.
那么一般来说,从构造函数抛出对于构造过程中的获取错误是可以的,并且符合RAII(资源获取是初始化)编程范式。
Check this example on RAII
void write_to_file (const std::string & message) {
// mutex to protect file access (shared across threads)
static std::mutex mutex;
// lock mutex before accessing file
std::lock_guard<std::mutex> lock(mutex);
// try to open file
std::ofstream file("example.txt");
if (!file.is_open())
throw std::runtime_error("unable to open file");
// write message to file
file << message << std::endl;
// file will be closed 1st when leaving scope (regardless of exception)
// mutex will be unlocked 2nd (from lock destructor) when leaving
// scope (regardless of exception)
}
Focus on these statements:
专注于这些陈述:
static std::mutex mutex
std::lock_guard<std::mutex> lock(mutex);
std::ofstream file("example.txt");
static std::mutex mutex
std::lock_guard<std::mutex> lock(mutex);
std::ofstream file("example.txt");
The first statement is RAII and noexcept
. In (2) it is clear that RAII is applied on lock_guard
and it actually can throw
, whereas in (3) ofstream
seems not to be RAII , since the objects state has to be checked by calling is_open()
that checks the failbit
flag.
第一个语句是 RAII 和noexcept
. 在 (2) 中很明显 RAII 被应用于lock_guard
并且它实际上可以throw
,而在 (3) 中ofstream
似乎不是 RAII ,因为必须通过调用is_open()
检查failbit
标志来检查对象状态。
At first glance it seems that it is undecided on what it the standard wayand in the first case std::mutex
does not throw in initialization , *in contrast to OP implementation * . In the second case it will throw whatever is thrown from std::mutex::lock
, and in the third there is no throw at all.
乍一看,它似乎尚未确定标准方式,并且在第一种情况下std::mutex
不会引发初始化,*与 OP 实现相反*。在第二种情况下,它将抛出从 抛出的任何内容std::mutex::lock
,而在第三种情况下根本没有抛出。
Notice the differences:
注意区别:
(1) Can be declared static, and will actually be declared as a member variable (2) Will never actually be expected to be declared as a member variable (3) Is expected to be declared as a member variable, and the underlying resource may not always be available.
(1) 可以声明为静态的,并且实际上会声明为成员变量 (2) 实际上永远不会期望声明为成员变量 (3) 预期会声明为成员变量,而底层资源可能不总是可用。
All these forms are RAII; to resolve this, one must analyse RAII.
所有这些形式都是RAII;要解决这个问题,必须分析RAII。
- Resource : your object
- Acquisition ( allocation ) : you object being created
- Initialization : your object is in its invariant state
- 资源:你的对象
- 获取(分配):您的对象正在被创建
- 初始化:您的对象处于不变状态
This does not require you to initialize and connect everything on construction. For example when you would create a network client object you would not actually connect it to the server upon creation, since it is a slow operation with failures. You would instead write a connect
function to do just that. On the other hand you could create the buffers or just set its state.
这不需要您在构建时初始化和连接所有内容。例如,当您创建一个网络客户端对象时,您实际上不会在创建时将它连接到服务器,因为它是一个缓慢的操作,会出现故障。你会写一个connect
函数来做到这一点。另一方面,您可以创建缓冲区或仅设置其状态。
Therefore, your issue boils down to defining your initial state. If in your case your initial state is mutex must be initializedthen you should throw from the constructor. In contrast it is just fine not to initialize then ( as is done in std::mutex
), and define your invariant state as mutex is created. At any rate the invariant is not compromized necessarily by the state of its member object, since the mutex_
object mutates between locked
and unlocked
through the Mutex
public methods Mutex::lock()
and Mutex::unlock()
.
因此,您的问题归结为定义您的初始状态。如果在你的情况下你的初始状态是mutex 必须被初始化,那么你应该从构造函数中抛出。相比之下,最好不要初始化 then (就像在 中所做的那样std::mutex
),并将您的不变状态定义为mutex is created。无论如何,不变量不一定会受到其成员对象的状态的影响,因为mutex_
对象在公共方法和之间locked
和unlocked
通过Mutex
公共方法Mutex::lock()
和 发生变化Mutex::unlock()
。
class Mutex {
private:
int e;
pthread_mutex_t mutex_;
public:
Mutex(): e(0) {
e = pthread_mutex_init(&mutex_);
}
void lock() {
e = pthread_mutex_lock(&mutex_);
if( e == EINVAL )
{
throw MutexInitException();
}
else (e ) {
throw MutexLockException();
}
}
// ... the rest of your class
};