Tuesday, March 7, 2017

C++11 features in Opendtect

OpendTect is since a couple of weeks using standard C++11 compliant compilers on all platforms, and we are therefore ready to start using some of the features in the updated language, while we will refrain from others. Here are the features that can be used in OpendTect:

Class variable assignment at declaration

C++11 allows assignment of variables directly in the class declaration:

mExpClass(Basic) A
{
    int b = 1;
};

If there are no further intelligence in the constructor, the constructor does not need to be defined or implemented as the assignment of the variables are done automatically. If you want to have it at a different value in a specific constructor, you can override the assignment in the constructor:

A::A()
    : b( 2 )
{}

Using this functionality, we can remove macros that are common with multiple constructors, where the sole use of the macro is to assign variables to default values.

Constructor delegation

You can now let one constructor call another constructor:


A::A( const char* str )
    : is_same_( strcmp(str,"mystring)==0 )
{}


A::A( const OD::String& str )
    : A( str.str() )
{}
    
Again, this can be useful as you can put all intelligence in one constructor, and have the other constructors call it.

Explicitly not creating operators or constructors

Every now and then, there are classes where you explicitly do not want the copy constructor or assignment operator. When the compiler wants these, it will normally try to create them automatically, which may lead to unwanted conversions between types. You can now explicitly forbid the compiler to do this by declaring a member "= delete":

mExpClass(Basic) A
{
public:
        A();
        A(const A&)         = delete;
    A&  operator=(const A&) = delete;
};

If you attempt to use one of these functions, the compiler will stop you at compile time.

R-Value references

C++ 11 allows references to R-Values using a double &&. If you know what you are doing, you may use these in constructors and assignment operators to create more effective assignments and constructors.


Sunday, October 30, 2016

bool + messages = uiRetVal

Often you need to return whether something worked or not. If it didn't, you need to provide a message. That message may actually be a series of messages. And all users need to be able to understand it, also those that cannot read English. In short, you want to return:
  1. The status (OK/Fail)
  2. One or more uiString's if the operation failed
We see this a lot in current code:
uiString errmsg;
if ( !myobj.doIt( errmsg ) )
   uiMSG().error( errmsg );
The new way is to use a uiRetVal. It is implemented as a set of uiStrings. OK means the list is empty. It's really simple. In new interfaces I now routinely use this object, and it works really well. Above example would become:
uiRetVal rv = myobj.doIt();
if ( rv.isError() )
    uiMSG().error( uirv );
Advantages:
  • uiRetval tells you OK/Fail but also has any number of messages with it
  • You cannot 'forget' setting the error message(s)
  • It works really smooth.
Example from the trenches:

uiRetVal Well::Saver::doStore( const IOObj& ioobj ) const
{
    uiRetVal uirv;
    ConstRefMan wd = wellData();
    if ( !wd )
        return uirv; // all OK, that's uiRetVal's default

    Writer wrr( ioobj, *wd );
    const StoreReqs sreqs = curstorereqs_.getObject();
    if ( sreqs.isAll() )
    {
        if ( !wrr.put() )
            uirv.add( wrr.errMsg() );
    }
    else
    {
        if ( sreqs.includes(Trck) && !wrr.putInfoAndTrack() )
            uirv.add( wrr.errMsg() );
        if ( sreqs.includes(D2T) )
        {
            if ( !wrr.putD2T() )
                uirv.add( wrr.errMsg() );
            else if ( wd->haveCheckShotModel() && !wrr.putCSMdl() )
                uirv.add( wrr.errMsg() );
        }
        if ( sreqs.includes(Mrkrs) && !wrr.putMarkers() )
            uirv.add( wrr.errMsg() );
        if ( sreqs.includes(Logs) && !wrr.putLogs() )
            uirv.add( wrr.errMsg() );
        if ( sreqs.includes(DispProps) && !wrr.putDispProps() )
            uirv.add( wrr.errMsg() );
    }

    curstorereqs_.getObject() = StoreReqs::All();
    return uirv;
}

Monday, September 19, 2016

Multiple instances of template classes on Windows

A common problem on Windows is that the linker complains over multiple instances of classes, most often template classes.
The reason for this is that normally, template classes are instantiated where they are used, and their binary code is not exported, i.e. they are not included in the content-list of the library they are baked into.

On Windows, an exported class is required to have all functions compiled and ready in the  library itself, including functions of inherited classes. All these functions are also exported, i.e. added to the content list of the library. Consider the following code(s):


In library Basic (a.h)
template <class T>
class A
{
    public:
                    A();
    int             compute() const;
};

As this is a template class, it is not exported, and no instances are added to the Basic.dll's list of contents.

In library "General" (b.h)

mExpClass(General) B : public A<int>
{
    ...
};

Here the compiler will export all functions in B and all functions in A<int>. This means that A<int>::compute() will be present on General.dll's list of contents. So far so good. The problem does however come if another library does the same:


In library "Well" (c.h)

mExpClass(General) C : public A<int>
{
    ...
};

As the compiler does not know that A<int> is already available in "General", it will instantiate all functions of A<int> into Well.dll just as when the "General" library was compiled. Hence, the linker will complain that there are two versions available, the one in Well.dll and the one from General.dll.

In OpendTect, we have solved this problem by explicitly telling that template classes that are instantiated in each library. This is done in the *export.h files. Everyone who is using a class from say the "General" library will include one or more headers from the General library. These files in turn include the "generalmod.h" header, which in turn includes the "generalexport.h" header.

generalexport.h looks like this:

#ifdef do_import_export
# include <a.h>

mExportTemplClassInst( General ) A<int>;

#endif

Hence, everyone who uses any class from General, will include this. The two macros essentially tell every user of General that A<int> is available in the "General" library. In the example above, the compiler of the class "C" would not create a new instance of A<int> as it would know that it is already done in the "General" library.

If you get problems with multiple instances, figure out in which module the template class is instantiated first, and add it to the export header of that module.

Thursday, September 15, 2016

Use of #pragma once

Fellow developers,

a short note that I have just committed a large change where we go from using double-include protection using

#ifndef filename_h
#define filename_h

...

#endif

But using the simpler, and less error prone

#pragma once

in the top of the file. The ifdef in the end of the file goes away.

Cheers,


Kristofer

Friday, May 20, 2016

Pick:Set's and their Manager

Well, I did it. Just like that, I committed weeks of work to make Pick::Set's (more) MT-safe, and unify the I/O handling of them. What I did wouldn't have been possible without Kris' work with the new RefMan<T> object handling. Here goes.



Monitorable


First of all, Pick::Set's are now Monitorable. Such an object promises to be MT-compliant, and notify changes in its state. That means, for example, multi-read locking. So no public data members or even references to internal, everything is done via get and set functions. For 'loose' Set's, unstored just for work, everything stays the same - albeit that you can no longer access internals directly. Oh and they have to be created like:
RefMan<Pick::Set> ps = new Pick::Set;
Because of the ref counting there is no longer a destructor. Small cost for big upside; and you'll get used to it.


The SetMGR


Then for everything stored, there is a new Pick::SetMGR() object. You ask it for a set with a set ID (the IOObj's MultiID), and it will make sure it's loaded. Like in:
uiString errmsg; 
ConstRefMan<Pick::Set> ps = Pick::SetMGR().fetch( id, errmsg ); 
if ( !errmsg.isEmpty() ) 
    { errmsg_ = errmsg; return false; }
The Set you are looking for may already be in memory, but it may also be read from disk. All you know is you get a ref, and other objects may also be looking at the same set. Or even, they may be changing it.


Monitoring a Set


If I want to know about some change, then:
mAttachCB( ps->objectChanged(), MyObj::pickSetChgCB );
By unpacking the 'Capsule' in the callback you can figure out what actually happened - but that's the topic of some other post. The thing is, the Pick::SetMGR() is not the keeper of events, like the old Pick::Mgr() was. Consequently, the new SetMGR is all about storing and sharing. Almost like a real OO database.
For example, all Set's, being derived from Monitorable, have a dirtyCount(). The SetMGR keeps special objects called Pick::SetSaver that keep track of the last saved dirty count so they know whether a Set was changed since it was added, read, or last saved.
If your stuff needs to be MT-ready, you'll want some kind of lock to prevent others from changing the Set while you are iterating over it. That is there, it's called a MonitorLock. Typically, one would do:
MonitorLock ml( *ps ); 
for ( int idx=0; idxsize(); idx++ ) 
{ 
    const Coord pos = ps->getPos( idx ); 
    // etc. 
} 
ml.unlockNow();
One thing: do not try to change the set while iterating. That is, unless you want to study deadlocks. Editing a set while having a MonitorLock on is simply not possible. Collect the changes may be an (MT-unsafe) idea. Or make a copy, and edit that. Then to finish copy back:
RefMan<Pick::Set> editedset = new Pick::Set( *ps ); 
doChanges( *editedset ); 
*ps = *editedset;


New Sets


To add a set, you could do something like:
RefMan<Pick::Set> ps = new Pick::Set;
fillPS( *ps );
uiString errmsg = Pick::SetMGR().store( *ps, id );
if ( !errmsg.isEmpty() )
    { errmsg_ = errmsg; return false; }
So, how do you get an ID? Well, quite possibly from the uiPickSetIOObjSel in uipicksettools.h. If you want no user interaction, you can still just give the Set a good name and store it with that:
uiString errmsg = Pick::SetMGR().store( *ps );
That will work, but you may just overwrite something, maybe something precious that another object is working with. Try:
if ( Pick::SetMGR().nameExists(ps->name()) )
{
    // Hmmm ... what now?
}


What's next?


On my TODO list is now to bring back undo handling (better than it was) and, the experienced MVC people probably guessed, burst event handling. My plan is to do that via a new object called ChangeNotifyBlocker combined with 'Entire object changed' change data.
And then ... we may have a template for other objects, to make them MT-ready, sharable, monitorable, tranparently stored. Wavelets seem a (rather simple) candidate, much more difficult but probably very rewarding may be wells.

Thursday, April 21, 2016

Effective Enum Handling in UI

Enums in OpendTect have three different aspects:
  1. The enum value
  2. The Key string (mostly used in IOPar, also going to text files)
  3. The UiString as shown in the user interface, say in a uiComboBox.

To enable a convenient transformation from one aspect to another we have the class EnumDef which manifests itself in a derived template class  EnumDefImpl<ENUM>. For each enum type, the corresponding EnumDef should be declared in the header file using the macro mDeclareEnumUtils or mDeclareNameSpaceEnumUtils. See enums.h for details. For example:

class MyClass
{
    enum State    { Good, Bad, OK, Undef };
                   mDeclareEnumUtils(State)


Then in the source file we need to define the keys using the macro mDefineEnumUtils:

 mDefineEnumUtils(MyClass,State,"My class state")
    { "Good", "Bad", "OK", "Undefined", 0 }

The strings above are the keys that would be useful in an IOPar or while writing to a file. By default the same strings will also be used in the UI. But if you want to use a different set of strings in UI, you can optionally define the uiStrings:

template <>
void EnumDefImpl<MyClass::State>::init()

{
    uistrings_ += tr("Lovely");
    uistrings_ += tr("Terrible");
    uistrings_ += toUiString("OK");
    uistrings_ += tr("Not sure");  
}

To use them in the UI, you can directly pass the EnumDef to classes like uiComboBox and uiStringListInpSpec. To set the value you can use:

combobox_->setCurrentItem( MyClass::StateDef().indexOf(oldval) );

And to get the value from the UI as enum you can do:

MyClass::State inp = MyClass::StateDef().getEnumValForIndex(
                                combobox_->currentItem() );

While reading from or writing to an IOPar, you should always use the enum key and the functions:

    EnumDef::indexOf(const char*)
    EnumDef::getKeyForIndex(int)

If you want more customization for a particular UI class, you can make your own copy of the EnumDef and make modifications like changing the uiStrings, removing some of the enums etc. For example see uiprestackattrib.cc.

The bottom line is, we have a lot of tools available, but to keep the code safe we need to take care of the following:
  • Always use the enum type (and not their int value) as class members or function arguments.
  • In IOPar or text files always write the key strings (not the int values or strings derived from uiStrings).
  • For existing enum types you can modify the uiStrings but never change the key strings if you want to maintain any backward compatibility with old files.


Monday, April 4, 2016

Changes to the DataPack and DataPackMgr classes

Following up on last week's blog, we are going further with re-engineering of some basic classes in preparation for the next major release.

To ensure thread safety, the DataPack classes are receiving an overhaul. The largest change is that the DataPack class is now reference counted, sharing the code with the rest of all reference counted objects. This ensures thread safety and a central knowledge about reference counting.

The major changes are:
  • DataPack::obtain() is replaced by ref().
  • DataPack::release() is replaced by unRef()
  • DataPackMgr::addAndObtain() going away. Your code should instead simply call DataPackMgr::add(). Care should be taken that you reference your object apropriately, as add() only makes the DataPackMgr aware of the object, it does not reference it.
  • DataPackMgr::obtain() is replaced by DataPackMgr::get() to get the object.
  • DataPackMgr::release() should be replace by calling either DataPackMgr::unRef(ID), or calling the unRef() on the object itself.
The old functionality still works as before, but we are phasing out its usage over the coming weeks. For the next major release, these functions will be marked as obsolete, and eventually be removed.

There is also new, nice functionality in there, such as DataPackMgr::getAndCast<T>( id ). By using it the following code:

RefMan<PreStack::Gather> = 0;
DataPack* dp = DPM(DataPackMgr::FlatID).obtain( id );
if ( dp )
{
    mDynamicCast( PreStack::Gather*, gather, dp );
    if ( !gather ) DPM(DataPackMgr::FlatID).release( dp );
}

can be replaced by:

RefMan<PreStack::Gather> gather 
        = DPM(DataPackMgr::FlatID).getAndCast<PreStack::Gather>(id);

As part of these changes, we are also investigating the thread safety at various places. So you can expect some API changes. For example, we are more and more using RefMan<T> rather than bare pointers.