1

I have a class Tiles that looks something like this:

class Tiles {
 public:
  void AddTile(int x) { tiles_.push_back(x); }
  std::vector<int> tiles_;
}

Now I want to create a class Tiles with data, i.e.,

class TilesWithData : public Tiles {
 public:
  void AddTile(int x, double data) { 
    tiles_.push_back(x);
    data_.push_back(data);  
  }
  std::vector<double> data_;
}

In this scenario I want that if a TilesWithData is created, the AddTile(int x) deleted, so that only the version AddTile(int x, double data) is available. Is that possible? What is the best design pattern for this case? Thanks!

1
  • 1
    Could you elaborate your use-case? Generally speaking what you described sounds a bit as if your design violates the LSP (see here), or at least the principle of least astonishment, but it's a bit hard to tell without knowing what you are trying to achieve. – Paul Kertscher Feb 8 '18 at 12:52
8

What behaviours do these classes have? At the moment you'd do better with

using Tile = int;
using Tiles = std::vector<Tile>;

struct TileWithData { Tile tile; double data; };
using TilesWithData = std::vector<TileWithData>;

The simple answer to your question is prefer composition over inheritance

5

TilesWithData is publicly derived from Tiles, hence it can be assigned to a (smart or dumb) pointer of type Tiles (my C++ is a bit rusted, but basically this should be sound)

Tiles *tiles = new TilesWithData();

Disallowing tiles->AddTile(123) would violate the Liskov Substitution Principle (see here), since a client that has a pointer to a Tiles instance (be it an actual Tiles or a TilesWithData) would not know not to call AddTile.

If there is a common functionality, you might introduce an abstract base class for both

class Tiles 
{
    public:
        virtual vector<int> GetTiles() = 0; 
}

from which you can derive SimpleTiles

class SimpleTiles : public Tiles 
{
    public:
        void AddTile(int x) { tiles_.push_back(x); }
        std::vector<int> GetTiles 
        {
            return tiles_;
        }
    private:
        std::vector<int> tiles_;
}

and TilesWithData

class TilesWithData : Tiles
{
    public:
        void AddTile(int x, double data) 
        { 
            tiles_.push_back(x); 
            data_.push_back(data);
        }

        std::vector<int> GetTiles 
        {
            return tiles_;
        }

        std::vector<double> GetData()
        {
            return data_;
        }
    private:
        // elided
}

This way, the data (internals) of your class is encapsulated and you are free to change the actual internal implementation without breaking the clients of your code. Furthermore, any client, holding a reference to Tiles can be sure about its capabilities: Getting the list of tiles. Anything further is up to the derivatives.

Remarks: There is still plenty of space for improvements. The Tiles implementations are neither fish nor meat, they don't provide real logic, but they are neither simple collections. A better way could be abstrac tiles

class Tile
{
    public:
        virtual void Update() = 0;
}

and a single collection to hold all types derived from Tile

class Tiles
{
    public:
        void AddTile(std::shared_ptr<Tile> tileToAdd)
        {
            tiles_.push_back(tileToAdd);
        }

        void UpdateTiles()
        {
            for(std::vector<std::shared_ptr<Tile>>:iterator it = tiles_.begin(); it != tiles_.end(); it++)
            {
                (*it)->Update();
            }
        }
    private:
        std::vector<std::shared_ptr<Tile>> tiles_;
}

Now you can implement different types of tiles suited to your needs

class SimpleTile : public Tile
{
    public:
        SimpleTile(int whatever)
        {
            // ...
        }

        void Update()
        {
            // ...
        }
    // ...
}

class TileWithData : public Tile 
{
    public:
        TileWithData(int whatever, double data)
        {
            // ...
            data_ = data;
        }

        void Update()
        {
            // ...
        }
    // ...
}

Disclaimer: It might well be that there are some errors in my C++ syntax. If my intention is clear, feel free to edit, otherwith, please leave me a comment.

1
2

The Template way of life

The only thing in common between Tiles and TilesWithData seem to be that they do similar things but with different data type. So inheritance is not well suited. You should prefer a generic Tiles instead:

template <class DataType>
class Tiles {
 public:
  void AddTile(DataType x) { tiles_.push_back(x); }
  std::vector<DataType> tiles_;
};

Once this is defined, you can use any kind of tiles:

Tiles<int> a; 
a.AddTile(10); 

Tiles<std::string> b; 
b.AddTile("hello, world"); 

Tiles<std::tuple<int, double>> c; 
c.AddTile(std::make_tuple(20, 12.5));

Note the last example: you could use your own type. But if you just want to freely use a combination of several types without bothering, you can use std::tuple.

Online demo


In case you'd prefer to keep inheritance...

Design issues

There are several problems with your design:

  • first, the data members should be private or protected. What's the sense of worrying about the illegitimate use of AddTile(), if on the other hand, anybody could alter tiles_ directly with a push_back() ?
  • second, TilesWithData::AddTile() should not mess with tiles_ directly, but rather use the base class interface (i.e. replace the explicit pushback with Tiles::AddTile(x);). Why ? Just imagine what would happen if you'd decide to switch to std::list tiles_; ...
  • third, you use inheritance improperly: if TilesWithData has a different interface, it is not really a kind of Tiles (and back to my first proposal ;-)

And it already works as you want !

With your classes, your scenario works already as desired ! This is because of the name hiding. For example, the following code will result in an error message:

TilesWithData b; 
b.AddTile(30);   /// OUCH !

The error message is:

prog.cpp:26:14: error: no matching function for call to ‘TilesWithData::AddTile(int)’
  b.AddTile(30);
              ^

Online demo

If you wanted to use the base classe's AddTile() you'd need to "import" its name by inserting following declaration in TilesWithData for the overload to work properly:

using Tile::AddTile; 

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.