5

In my project I found a piece of code in which a method was getting called in constructor's initializer list.

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

I observed that there is a chance that the users of Test2 might pass NULL to the constructor. Since the pointer is used without validation there are chances of Access Violation.

This triggered me to look into exception handling in constructor's initializers list. I found in one of the article that try can be used inside initializer list. I wrote small test program to test this concept:

//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;
}

This code is not getting compiled in VC6.0. Do I need to make any changes to make it compile in VC 6.0?

Also, in one of the article I found that using try in constructor's initializer list does not strictly confirms to C++ standards. In that case, how do we handle the exceptions in constructor's initializers list (standard way of handling )?

Thanks.

aJ.
  • 34,624
  • 22
  • 86
  • 128

7 Answers7

12

C++ Standard Section 15 / 3

A function-try-block associates a handler-seqwith thector-initializer, if present, and the function-body. An exception thrown during the execution of the initializer expressions in the ctor-initializer or during the execution of the function-body transfers control to a handler in a function-try-block in the same way as an exception thrown during the execution of a try-block transfers control to other handlers.

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  
}
Mykola Golubyev
  • 57,943
  • 15
  • 89
  • 102
8

Firstly, if you dereference the NULL pointer standard C++ does not guarantee that that an exception will be thrown, so your code is useless for this case.

Secondly, if an exception were thrown, what would your exception handler do?

Thirdly, constructor/function exception blocks are widely considered to be awaste of time - take a look at this http://www.gotw.ca/gotw/066.htm and other articles on Herb Sutter's GotW site.

  • 1
    Even pTest->getTestID() in the constructor could throw an exception. – aJ. Mar 30 '09 at 12:54
  • 1
    You don't do Herb's article justice: construction exception blocks are to be used only for rethrowing different exceptions, or for side effects such as logging. For those purposes, they certainly aren't a waste of time. They just can't do more than that. – Pontus Gagge Mar 30 '09 at 13:00
  • Yeah, don't go there with the constructor exception stuff. Just check the input argument and assert or throw as appropriate. – Brian Neal Mar 30 '09 at 13:02
4

According to this article, it looks like you just can't do that in VC++ 6.0

You'd either have to upgade to 7.0 or just do the initialization in the constructor body instead.

Eric Petroelje
  • 59,820
  • 9
  • 127
  • 177
3

Can't you just use a function to check the ptr, e.g.:

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;
};
jon hanson
  • 8,722
  • 2
  • 37
  • 61
1

People still use VC6? Seriously, VC6 is hardly a standards-complaint compiler. Do yourself a favor and at least get VS2005. VC6 is your problem. Try VS2008 express and see if it compiles.

The other option, of course, is to take a reference on construction, which needs to be bound.

rlbond
  • 65,341
  • 56
  • 178
  • 228
  • Unfortunately, there are many multi-million line monolithic C++ applications in VC6 which it would cost many developer years to port to modern C++, and many companies don't want to make the switch. – Pete Kirkham Apr 09 '09 at 09:56
  • @Pete: these companies don't understand asymptotic growth then. Growth of cost, that is. Maintaining such ancient code doesn't come cheap, and it doesn't get cheaper. – Konrad Rudolph Apr 09 '09 at 10:28
  • 2
    I agree, but you don't always get enough influence to make the change (particularly as a contractor), and often there are many more pressing issues to deal with. – Pete Kirkham Apr 09 '09 at 13:42
0

(for fellow googlers)

Another solution if we don't want to store a copy of the ptr / shared_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
}
Offirmo
  • 18,962
  • 12
  • 76
  • 97
0

There are many useful answers already, but I'll try to add a little bit, maybe it will help someone.

First of all, as others already mentioned - dereferencing a nullptr or invalid pointer (address) doesn't throw an exception in the standard C++. MSVC supports it through its Structured Exception Handling, but it's not portable. Read more about it in this answer.

The function try block in a constructor doesn't allow you to suppress the exception, it will propagate anyway if you don't throw another one. And when you enter the catch clause, all members of the class are already destroyed. So the only plausible thing you can do in it is to log the error somehow or maybe change some global variables. That's why it's considered more or less useless.

As for your initial code

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

you could use the ternary operator to check for pTest's nullness just in the initializer list and do some appropriate actions in case it is null - just set m_nDuplicateID to nullptr or some other value depending on its type, call another function and use it return type, etc:

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

you could even use several nested ternary operators to create more complicated execution flows.

And just for completeness, it's not the case with your code, but it may worn someone in the same situation. If you used your class's member m_pTest to initialize m_nDuplicateID, that would depend on the order of these members in class's declaration, because class members in the initializer list get initialized in the order of declaration and not in the order they appear in the initializer list itself, so it can be a problem and it's better to avoid members initialization order dependencies:

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
{
}
Community
  • 1
  • 1
Roman Kruglov
  • 3,375
  • 2
  • 40
  • 46