C++ 构造函数中的异常处理&x2019;s初始值设定项列表

C++ 构造函数中的异常处理&x2019;s初始值设定项列表,c++,exception-handling,C++,Exception Handling,在我的项目中,我发现了一段代码,其中在构造函数的初始值设定项列表中调用了一个方法 Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID()) { } 我注意到Test2的用户可能会将NULL传递给构造函数。由于指针未经验证就被使用,因此存在访问冲突的可能性 这促使我研究构造函数的初始值设定项列表中的异常处理。我在一篇文章中发现,try可以在初始值设定项列表中使用。我编写了一个小测试程序来测试这

在我的项目中,我发现了一段代码,其中在构造函数的初始值设定项列表中调用了一个方法

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;
}
//测试类存储唯一的ID,并使用API getTestID返回相同的ID
课堂测试
{
公众:
测试(int nID):m_nID(nID){
}
int getTestID()常量
{
返回m_nID;
}
私人:
国际货币基金组织;
};
类Test2
{
公众:
测试2(测试*pTest)
try:m_-pTest(pTest),m_-nDuplicateID(pTest->getTestID())
{
}
捕获(…)
{
cout根据,在VC++6.0中似乎无法做到这一点


您必须要么将Uggad 7或只是在构造函数体中初始化,否则,

首先,如果您引用空指针,标准C++不保证会抛出异常,因此您的代码对于这种情况是无用的。 其次,如果抛出异常,您的异常处理程序会做什么


第三,构造函数/函数异常块被广泛认为是时间的象征——看看Herb Sutter的GotW站点上的这篇文章和其他文章。

C++标准第15/3节

函数try块与 使用Tor初始值设定项, 如果存在,以及功能体 执行期间引发异常 中的初始值设定项表达式的 ctor初始值设定项或在 功能体的执行 将控制权转移到 函数try块的方式与 测试过程中引发的异常 执行try块传输 将控件复制到其他处理程序

人们还在使用VC6吗? 说真的,VC6不是一个标准投诉编译器。帮你自己一个忙,至少得到VS2005。VC6是你的问题。 试试VS2008 express,看看它是否可以编译


当然,另一个选项是引用需要绑定的构造。

您不能只使用一个函数来检查ptr,例如:

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;
};
模板
P*checkPtr(P*P)
{
如果(p==0)
抛出std::runtime_错误(“空指针”);
返回p;
}
类Test2
{
公众:
测试2(测试*pTest)
:m_pTest(checkPtr(pTest))
{
}
测试*m_测试;
};
(适用于谷歌同行)

如果我们不想存储ptr/共享ptr的副本,另一种解决方案是:

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
}

已经有很多有用的答案了,但我会尝试补充一点,也许它会帮助一些人

首先,正如其他已经提到的,取消代码< NulLPTR >代码>或无效指针(地址)不会在标准C++中引发异常。MSVC支持它,但它不是可移植的。请在./P>中阅读更多内容。 构造函数中的函数try block不允许您抑制异常,如果您不抛出另一个异常,它将传播。当您输入catch子句时,该类的所有成员都已被销毁。因此,您可以在其中做的唯一合理的事情是以某种方式记录错误,或者可能更改某些全局变量。这就是为什么它或多或少被认为是无用的

至于你的初始代码

Test2(Test* pTest):m_pTest(pTest), m_nDuplicateID(pTest->getTestID())
    {
    }
您可以使用三元运算符仅在初始值设定项列表中检查pTest的null值,并在其为null时执行一些适当的操作-只需将m_nDuplicateID设置为null ptr或其他一些值(取决于其类型),调用另一个函数并使用其返回类型,等等:

Test2(Test* pTest):
    m_pTest(pTest),
    m_nDuplicateID( pTest ? pTest->getTestID() : /*some value or call*/ )
{
}
您甚至可以使用几个嵌套的三元运算符来创建更复杂的执行流

为了完整起见,您的代码并非如此,但可能有人在同样的情况下使用了它。如果您使用类的成员m_pTest初始化m_duplicateId,这将取决于这些成员在类声明中的顺序,因为初始化器列表中的类成员是按照声明的顺序初始化的,而不是t按照它们在初始值设定项列表中出现的顺序,因此这可能是一个问题,最好避免成员初始化顺序依赖关系:

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
{
}

我会选择升级,因为初始化列表有时无法避免(例如初始化引用)在构造函数中可能会抛出异常。你不可能做到赫伯的文章:构造异常块只能用于重新引用不同的异常,或用于日志记录等副作用。出于这些目的,它们当然不会浪费时间。它们不能做更多。是的,不要使用构造函数异常只需检查输入参数,并适当地声明或投掷。“Eric P”的回答中的文章称它不符合标准。除了此之外,我想知道为什么VC 6不支持。从文章“你不能在Visual C++ 6中编译它,因为它不严格地确认C++标准。”VC++ 6在标准VC6之前就因为不符合标准C++而臭名昭著。不幸的是,在VC6中有很多百万线的单片C++应用程序,它将花费很多开发者年来移植到现代C++中,许多公司不想进行切换。n、 也就是说,成本的增长。维护这些古老的代码并不便宜,也不会变得更便宜。我同意,但你并不总能获得足够的影响力来进行更改(特别是作为一名承包商),而且通常还有许多更紧迫的问题需要处理。
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
{
}