I have a class with a static member:
class MyClass
{
public:
static const SomeOtherClass myVariable;
};
Which I initialize in the CPP file like so:
const SomeOtherClass MyClass::myVariable(SomeFunction());
The problem is, SomeFunction()
reads a value from the registry. If that registry key doesn't exist, it throws an exception. This causes my program to explode without giving the user any useful output... is there some way I can catch the exception so I can log it?
I don't like static
data members much, the problem of initialization being foremost.
Whenever I have to do significant processing, I cheat and use a local static
instead:
class MyClass
{
public:
static const SomeOtherClass& myVariable();
};
const SomeOtherClass& MyClass::myVariable()
{
static const SomeOtherClass MyVariable(someOtherFunction());
return MyVariable;
}
This way, the exception will be throw only on first use, and yet the object will be const
.
This is quite a powerful idiom to delay execution. It had a little overhead (basically the compiler checks a flag each time it enters the method), but better worry about correctness first ;)
If this is called from multiple threads:
- if your compiler handles it, fine
- if your compiler does not, you may be able to use local thread storage (it's const anyway)
- you could use
boost::once
in the Boost.Threads
library
- since it's
const
, you may not care if it's initialized multiple times, unless someOtherFunction
does not support parallel execution (beware of resources)
Guideline: only use static
or global
variables instantiation for simple objects (that cannot throw), otherwise use local static
variables to delay execution until you can catch the resulting exceptions.
Perhaps the best thing to do would be to add the registry key to a list instead of looking it up, then as soon as main() is entered, go through and look up all of the keys in the list. I don't want to be preachy but situations like this are exactly why it's generally a bad idea to do significant processing before main() is entered.
Sure -- wrap SomeFunction()
in a function like:
int static_error;
void SomeFunctionWrapper() {
try {
SomeFunction();
}
catch(...) { // something more specific if possible
static_error = 1;
}
}
Then upon entry to main, you'll want to check for static_error != 0
and print an appropriate error message if needed (unfortunately, you can't know if std::cerr
exists yet in your exception handler, so if you want to print from there, you'll have to do something like C FILE*-based output).
You can wrap the function inside another function that catches the exception and alerts the user of the problem (or creates the key with a safe default value)
You could make a wrapper class that delays construction of the object. Then when its first used, it will throw where ever its first used, if the constructor throws.
This has the benefit of not having alot of code run before main() is called, and if you don't ever actually use the global object it won't ever be initialized.
The code:
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include <boost/scoped_ptr.hpp>
#include <boost/thread/once.hpp>
#include <iostream>
const boost::once_flag DEFAULT_ONCE_FLAG = BOOST_ONCE_INIT;
template <typename T>
class DelayedConstruction {
public:
DelayedConstruction(boost::function<T* (void) > const & init = &DelayedConstruction::default_initializer ) :
m_initializer(init), m_flag(DEFAULT_ONCE_FLAG) { }
T const & operator*() const {
boost::call_once(m_flag, boost::bind(&DelayedConstruction::initialize, this) ) ;
if ( ! m_object )
throw std::runtime_error("Object could not be initialized") ;
return *m_object ;
}
T const * operator->() const {
boost::call_once(m_flag, boost::bind(&DelayedConstruction::initialize, this) ) ;
if ( ! m_object )
throw std::runtime_error("Object could not be initialized") ;
return m_object.get() ;
}
static T* default_initializer() { return new T; }
private:
void initialize() const {
m_object.reset( m_initializer() ) ;
}
boost::function<T* (void) > m_initializer ;
mutable boost::scoped_ptr<T> m_object ;
mutable boost::once_flag m_flag ;
};
struct Foo {
Foo(int x = 0) : m_x(x) {
if ( x == 1 ) throw std::runtime_error("Can't be 1") ;
}
int m_x ;
} ;
Foo* make_custom_foo() {
return new Foo(1) ;
}
DelayedConstruction< const Foo > g_myFoo ;
DelayedConstruction< const Foo > g_anotherFoo(&::make_custom_foo) ;
int main() {
try {
std::cout << "My Foo: " << g_myFoo->m_x << std::endl ;
std::cout << "Another Foo: " << g_anotherFoo->m_x << std::endl ;
} catch ( std::runtime_error const & e ) {
std::cout << "ERROR: " << e.what() << std::endl ;
}
return 0 ;
}
Prints out:
My Foo: 0
ERROR: Can't be 1