Avoiding down-casts in C++

If you have to down-cast objects from a base class to a derived class then there’s probably a design flaw in the class structure. It might all work but it’s going to be a brittle design.

// C++ FAQS by Cline & Lowom
// FAQ 162 - What is a down-cast?
// Answer: Trouble.
 
#include <iostream>
 
using namespace std;
 
class Asset
{
	public:
		virtual ~Asset() { }
		virtual bool isLiquidatable() const { return false; } // BAD-FORM: Capability query
};
 
class LiquidAsset : public Asset
{
	protected:
		int mValue;
 
	public:
		LiquidAsset(int value = 100) : mValue(value) { }
 
		int getValue() const { return mValue; }
 
		void setValue(int theValue) { mValue = theValue; }
 
		virtual bool isLiquidatable() const { return true; } // BAD-FORM: Capability query
};
 
// Userland function to liquidate an asset (if it can)
int tryToLiquidate(Asset &asset)
{
	int value;
 
	if ( asset.isLiquidatable() )                     // BAD-FORM: Finds code using code
	{
		value = ((LiquidAsset&)asset).getValue(); // BAD-FORM: Down-cast
 
		((LiquidAsset&)asset).setValue(0);        // BAD-FORM: Down-cast
 
		cout << "Liquidated $" << value << endl;
	}
	else
	{
		value = 0;
		cout << "Sorry, couldn't liquidate this asset.\n";
	}
 
	return value;
}
 
int main()
{
    Asset       a;
    LiquidAsset b;
 
    tryToLiquidate(a);
    tryToLiquidate(b);
 
    return 0;
}

A better way to accomplish this is to give the users of your code the right tools in the first place as member functions, rather than them having to cobble together their own routines in userland:

// C++ FAQS by Cline & Lowom
// FAQ 163 - What is an alternative to using down-casts?
// Answer: An if/down-cast pair can often be replaced by a virtual function call. The key
// insight is to move the -context- of the capability query from the user's code into the
// virtual function; don't just move the primitive query used in the user's if statements.
 
#include <iostream>
using namespace std;
 
class Asset
{
	public:
		virtual ~Asset() { }
 
		virtual int tryToLiquidate()
		{
			cout << "Sorry, couldn't liquidate this asset.\n";
			return 0;
		}
};
 
class LiquidAsset : public Asset
{
	protected:
		int mValue;
 
	public:
		LiquidAsset(int value = 100) : mValue(value) { }
 
		int getValue() const { return mValue; }
 
		void setValue(int theValue) { mValue = theValue; }
 
		virtual int tryToLiquidate()
		{
			int value = mValue;
 
			mValue = 0;
 
			cout << "Liquidated $" << value << endl;
 
			return value;
		}
};
 
int main()
{
    Asset       a;
    LiquidAsset b;
 
    a.tryToLiquidate();
    b.tryToLiquidate();
 
    return 0;
}

Much better =D