构造函数初始化列表中的异常处理

5
在我的项目中,我发现了一段代码,在构造函数的初始化列表中调用了一个方法。
Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }

我注意到Test2的用户可能会将NULL传递给构造函数。由于指针在没有验证的情况下被使用,存在访问冲突的风险。

这促使我研究构造函数初始化程序列表中的异常处理。我在一篇文章中发现可以在初始化程序列表中使用try。我编写了一个小测试程序来测试这个概念:

//Test class stores the unique ID and returns the same with API getTestID
class Test
{
public:

    Test(int nID):m_nID(nID){
    }

    int getTestID() const
    {
            return m_nID;
    }
private:
    int m_nID;

};


class Test2
{
public:

    Test2(Test* pTest) 
        try :m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }
    catch (...) 
    {
        cout<<"exception cought "<< endl;
    }

    void printDupID()
    {
        cout<<"Duplicate ID" << m_nDuplicateID << endl;
    }
private:

    Test* m_pTest;
    int m_nDuplicateID;
};

int main(int argc, char* argv[])
{

    Test* pTest = new Test(10);


    Test2 aTest2(pTest);
    aTest2.printDupID();


    delete pTest;

    return 0;
}

这段代码无法在VC6.0中编译。我需要做出哪些修改才能让它在VC6.0中编译?

此外,在一篇文章中,我发现在构造函数的初始化列表中使用try不严格符合C++标准。在这种情况下,我们如何处理构造函数的初始化程序列表中的异常(标准处理方式)?

谢谢。


VC6以不符合标准C++而臭名昭著。 - Brian Neal
7个回答

12

C++标准第15/3节

函数尝试块将handler-seq与构造函数初始化器(如果存在)和函数体相关联。在构造函数初始化器或函数体执行期间抛出异常时,控制权会以与在try块中抛出异常相同的方式转移到位于同一函数尝试块中的处理程序。

class C 
{  
    int i;  
    double d;  
public:  
    C(int, double);  
};  

C::C(int ii, double id)  
try  : i(f(ii)), d(id)  
{  
//constructor function body  
}  catch (...)  
{  
//handles exceptions thrown from the ctor-initializer  
//and from the constructor functionbody  
}

"Eric P"的答案中提到,这不是标准用法。除此之外,我想知道为什么VC 6.0不支持它。 - aJ.
从文章中可以看出,"你无法在Visual C++ 6.0中编译此代码,因为它不严格遵循C++标准。" - Mykola Golubyev
VC++ 6.0发布于标准之前。 - anon

8
首先,如果您取消引用空指针,标准C++不保证会抛出异常,因此您的代码对于这种情况是无用的。
其次,如果抛出异常,您的异常处理程序会做什么?
第三,构造函数/函数异常块被广泛认为是浪费时间的 - 可以查看Herb Sutter的GotW网站上的http://www.gotw.ca/gotw/066.htm和其他文章。

1
即使在构造函数中,pTest->getTestID() 也可能会抛出异常。 - aJ.
1
你没有充分发挥Herb的文章:构造异常块只能用于重新抛出不同的异常,或者用于副作用,例如记录日志。对于这些目的,它们绝对不是浪费时间。只是不能做更多的事情。 - Pontus Gagge
不要在构造函数异常方面纠缠。只需检查输入参数并根据需要进行断言或抛出异常即可。 - Brian Neal

4
根据这篇文章,在VC++ 6.0中似乎无法这样做。您只能升级到7.0或者在构造函数体中进行初始化。

我会选择升级,因为有时候无法避免使用初始化列表(例如,用于初始化引用)。 - Luc Touraille

3

你不能仅仅使用一个函数检查指针吗?例如:

template<typename P>
P* checkPtr (P* p)
{
    if (p == 0)
        throw std::runtime_error ("Null pointer");
    return p;
}

class Test2
{
public:
    Test2 (Test* pTest)
        : m_pTest (checkPtr (pTest))
    {
    }

    Test* m_pTest;
};

1

人们还在使用VC6吗? 说真的,VC6几乎不符合标准的编译器。为自己着想,至少要使用VS2005。VC6是你的问题所在。 试试VS2008 express看看是否可以编译。

当然,另一个选择是参考构造函数,需要绑定。


不幸的是,有许多在VC6中的大型C++单体应用程序拥有数百万行代码,要将其迁移到现代C++需要花费数年开发人员的时间,而很多公司并不想进行转换。 - Pete Kirkham
@Pete:这些公司不理解渐近增长。成本的增长,也就是说。维护这样古老的代码并不便宜,而且价格也不会便宜。 - Konrad Rudolph
2
我同意,但你并不总是有足够的影响力来做出改变(特别是作为承包商),而且通常还有更紧迫的问题需要处理。 - Pete Kirkham

0

已经有很多有用的答案了,但我会尝试添加一点,也许会帮助到某些人。

首先,正如其他人已经提到的 - 解引用 nullptr 或无效指针(地址)在标准 C++ 中不会抛出异常。MSVC 通过其 结构化异常处理 支持它,但它不具备可移植性。在 这个答案 中可以了解更多信息。

构造函数中的函数 try 块不能让你抑制异常,如果你不抛出另一个异常,它仍然会传播。当你进入 catch 子句时,类的所有成员已经被销毁。因此,在其中唯一合理的事情是以某种方式记录错误或者可能更改一些全局变量。这就是为什么它被认为是几乎没有用处的原因。

至于你的初始代码

Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }

你可以使用三目运算符在初始化列表中检查 pTest 的空值,并在其为空时执行适当的操作 - 只需将 m_nDuplicateID 设置为 nullptr 或其他取决于其类型的值,调用另一个函数并使用其返回类型等:

Test2(Test* pTest):
    m_pTest(pTest),
    m_nDuplicateID( pTest ? pTest->getTestID() : /*some value or call*/ )
{
}

你甚至可以使用多个嵌套的三元运算符来创建更复杂的执行流程。

为了完整起见,虽然这不是你代码的情况,但它可能会警示处于相同情况的人。如果你使用类成员m_pTest来初始化m_nDuplicateID,那么这将取决于类声明中这些成员的顺序,因为初始化程序列表中的类成员按照声明顺序而不是它们在初始化程序列表本身中出现的顺序进行初始化,所以这可能会成为一个问题,最好避免成员初始化顺序依赖:

class A
{
    A( B* pTest );
    int m_nDuplicateID;
    B* m_pTest;
};

A::A( B* pTest ) :
    m_pTest( pTest ),
    m_nDuplicateID( m_pTest->someMethod() ) // here m_pTest isn't initialized yet,
                                            // so access violation probably
{
}

0
(给Google的同行们)
另一个解决方案是,如果我们不想存储指针/共享指针的副本:
class Foo::Pimpl
{
public:
    bool paramTest_;

    Pimpl(ConstNodePtr root)
    try  :
       paramTest_( root ? true : throw std::invalid_argument("Foo (pimpl) constructed from NULL node")),
    ...
{
    ...
}  catch (...)
{
    throw; // rethrow
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接