Inheriting and Overriding Functions of a Std::String

Inheriting and overriding functions of a std::string?

DON'T DERIVE FROM STRING

std::string, that is, basically the whole basic_string template is not designed to be derived from. There are zillions of articles about that already. It doesn't have any virtual functions so there is nothing to override. The best you can do is hide something. Best is to use composition/aggregation! That is, just keep a member of type string in your class and forward the calls! Again, just to make sure

DON'T DERIVE FROM STRING

inherit from std::string or operator overloading

You can provide a user-defined conversion operator to std::string:

class Test {
//...
public:
operator std::string () const {
return /*something*/;
}
};

This will allow a Test object to be implicitly-converted to a std::string.

Constructor doesn't work for class inherited from std::string

You need to define some constructors for the different types that you want to be able to convert into your strings. These constructors can basically just hand the parameters through to the underlying std::string.

If you don't manually create them, the compiler creates a default- and a copy-constructor for you:

MyString() : std::string() { }
MyString(const MyString &other) : std::string(other) { }

To allow construction from string literals, you need a constructor that takes a const char*:

MyString(const char* other) : std::string(other) { }

A constructor that takes a const std::string& would also be useful to convert std::strings to your string type. If you want to avoid implicit conversions of normal strings, you should make it explicit:

explicit MyString(const std::string &other) : std::string(other) { }

(Edited because my original version was full of errors and I can't delete the accepted answer)

How am i overriding this C++ inherited member function without the virtual keyword being used?

It's not overriding the ToString method in the base class as the base class method is not virtual. It is simply hiding that function with a function with an identical signature.

When you call ToString() on a Dog object the Dog::ToString method is called. Why would it call any other ToString() method; the Dog:: declaration is the first one found? virtual dispatch would only happen (and only be needed) when being called through a pointer or reference to a base class object.

If you needed to call the base class method on a Dog object you would have to qualify it explicitly.

d.Mammal::ToString()

What should I override in inheritance when it comes to simple derivation?

Do not derive from std::string. Some reasons are explained here, so I won't repeat them.

If you want to extend functionality of std::string, you can either

Make a wrapper class

class MyString
{
std::string underlying_string;
public:
/* methods */
}

Problem is, that it has nothing to do with std::string anymore. It can't be assigned to std::string, nor passed to function accepting std::string1. Also it is quite a bit verbose - consider adding all overloads of operator+ (std::string + MyString, MyString + std::string, maybe also MyString + char*, add consts and move semantics) and you are at ~16 methods?

Another option, is to define just helper method.

std::string do_super_cool_thing(const std::string& str, int a, int b)
{
return magic(str, a, b);
}

  1. Can be helped with non-explicit ctor MyString::MyString(std::string), user defined conversion operator std::string(), but it has different meaning than inheritance.

Why should one not derive from c++ std string class?

I think this statement reflects the confusion here (emphasis mine):

I do not understand what specifically is required in a class to be eligible for being a base clas (not a polymorphic one)?

In idiomatic C++, there are two uses for deriving from a class:

  • private inheritance, used for mixins and aspect oriented programming using templates.
  • public inheritance, used for polymorphic situations only. EDIT: Okay, I guess this could be used in a few mixin scenarios too -- such as boost::iterator_facade -- which show up when the CRTP is in use.

There is absolutely no reason to publicly derive a class in C++ if you're not trying to do something polymorphic. The language comes with free functions as a standard feature of the language, and free functions are what you should be using here.

Think of it this way -- do you really want to force clients of your code to convert to using some proprietary string class simply because you want to tack on a few methods? Because unlike in Java or C# (or most similar object oriented languages), when you derive a class in C++ most users of the base class need to know about that kind of a change. In Java/C#, classes are usually accessed through references, which are similar to C++'s pointers. Therefore, there's a level of indirection involved which decouples the clients of your class, allowing you to substitute a derived class without other clients knowing.

However, in C++, classes are value types -- unlike in most other OO languages. The easiest way to see this is what's known as the slicing problem. Basically, consider:

int StringToNumber(std::string copyMeByValue)
{
std::istringstream converter(copyMeByValue);
int result;
if (converter >> result)
{
return result;
}
throw std::logic_error("That is not a number.");
}

If you pass your own string to this method, the copy constructor for std::string will be called to make a copy, not the copy constructor for your derived object -- no matter what child class of std::string is passed. This can lead to inconsistency between your methods and anything attached to the string. The function StringToNumber cannot simply take whatever your derived object is and copy that, simply because your derived object probably has a different size than a std::string -- but this function was compiled to reserve only the space for a std::string in automatic storage. In Java and C# this is not a problem because the only thing like automatic storage involved are reference types, and the references are always the same size. Not so in C++.

Long story short -- don't use inheritance to tack on methods in C++. That's not idiomatic and results in problems with the language. Use non-friend, non-member functions where possible, followed by composition. Don't use inheritance unless you're template metaprogramming or want polymorphic behavior. For more information, see Scott Meyers' Effective C++ Item 23: Prefer non-member non-friend functions to member functions.

EDIT: Here's a more complete example showing the slicing problem. You can see it's output on codepad.org

#include <ostream>
#include <iomanip>

struct Base
{
int aMemberForASize;
Base() { std::cout << "Constructing a base." << std::endl; }
Base(const Base&) { std::cout << "Copying a base." << std::endl; }
~Base() { std::cout << "Destroying a base." << std::endl; }
};

struct Derived : public Base
{
int aMemberThatMakesMeBiggerThanBase;
Derived() { std::cout << "Constructing a derived." << std::endl; }
Derived(const Derived&) : Base() { std::cout << "Copying a derived." << std::endl; }
~Derived() { std::cout << "Destroying a derived." << std::endl; }
};

int SomeThirdPartyMethod(Base /* SomeBase */)
{
return 42;
}

int main()
{
Derived derivedObject;
{
//Scope to show the copy behavior of copying a derived.
Derived aCopy(derivedObject);
}
SomeThirdPartyMethod(derivedObject);
}

how to override operator== in inheritance without being limited to base class?

As a rule, objects of different types can't be equal to each other.

That being the case, for your case, you may be able to get at least reasonably sane results with code on this general order:

struct base {
std::string info;
virtual ~base() = default;

virtual bool cmp(base const& other) const {
return info == other.info;
}
};

struct derived1 : public base {
int i = 0;

bool cmp(base const& other) const override
{
try {
derived1 const& d = dynamic_cast<derived1 const&>(other);
return info == d.info && i == d.i;
}
catch(...) { return false; }
}
};

bool operator==(base const &a, base const &b) {
return a.cmp(b) && b.cmp(a);
}

The logic here may not be obvious. Instead of overloading == as a virtual member function, we do the overload as a free function. And the free function does two comparisons, once in each order.

We do that to deal with a case like:

base b;
derived1 d;

if (b == d) std::cout << "oops: shouldn't be equal\n";

If we had overloaded operator== as a member function, it would tell us that the two objects are equal, which is almost certainly not desired. It does that because the code is equivalent to: if (b.operator==(d)), which uses the base class comparison operator. Since a reference to the base can refer to an object of derived type, that works, and just compares the base class parts of the two objects, ignoring the fact that one is really a derived object, so the two can't be equal.

With the code the way it's written above, we use both b.operator==(d) and d.operator==(b), so if one of b or d is an object of derived type, then we'll only get a true result if the other is also of the (same) derived type. Any attempt at mixing objects of base and derived type will yield false.

So that does what I think you're asking for. But I'm a long ways from excited about this code even at best. First of all, if comparisons are expensive, it'll get pretty slow, since it does each comparison twice. Second, the code is fragile and difficult to maintain--at the very least, it requires a fairly detailed comment about why it's written the way it is, and why simpler, more obvious code won't work reliably.

Bottom line: yes it works (at least as I perceive what you want), but no I don't like or recommend it.

C++ How to override class field with a different type (without template)?

What you are attempting to do will not work, because TokenValue is a base class and you are storing it by value in Token, so if you attempt to assign a TV_String object, a TV_Int object, etc to Token::value, you will slice that object, losing all info about the derived class type and its data fields.

To work with polymorphic classes correctly, you will need to make the Token::value field be a pointer to a TokenValue object instead, eg:

class TokenValue
{
public:
virtual ~TokenValue() = default;

virtual bool equals(const TokenValue*) const = 0;

bool operator==(const TokenValue &rhs) const {
return equals(&rhs);
}
};

class TV_Empty : public TokenValue {
public:
bool equals(const TokenValue* tv) const override {
return (dynamic_cast<const TV_Empty*>(tv) != nullptr);
}
};

class TV_String : public TokenValue
{
public:
std::string value;

TV_String(const std::string &value) : value(value) {}

bool equals(const TokenValue* tv) const override {
TV_String *s = dynamic_cast<const TV_String*>(tv);
return (s) && (s->value == value);
}
};

class TV_Int : public TokenValue
{
public:
int value;

TV_Int(int value) : value(value) {}

bool equals(const TokenValue* tv) const override {
TV_Int *i = dynamic_cast<const TV_Int*>(tv);
return (i) && (i->value == value);
}
};

class TV_Float : public TokenValue
{
public:
float value;

TV_Float(float value) : value(value) {}

bool equals(const TokenValue* tv) const override {
TV_Float *f = dynamic_cast<const TV_Float*>(tv);
return (f) && (f->value == value);
}
};

...
struct EmptyToken {};

class Token
{
public:
enum class TokenType
{
Empty,
String,
Int,
Float
...;
};

TokenType type;
std::unique_ptr<TokenValue> value;

static TokenType GetTokenType(const TokenValue *tv) {
if (dynamic_cast<TV_Empty*>(tv) != nullptr)
return TokenType::Empty;
if (dynamic_cast<TV_String*>(tv) != nullptr)
return TokenType::String;
if (dynamic_cast<TV_Int*>(tv) != nullptr)
return TokenType::Int;
if (dynamic_cast<TV_Float*>(tv) != nullptr)
return TokenType::Float;
return ...;
}

Token(std::unique_ptr<TokenValue> value) : Token(GetTokenType(value.get()), std::move(value)) {}

Token(TokenType type, std::unique_ptr<TokenValue> value) : type(type), value(std::move(value)) {}

explicit Token(const EmptyToken &) : type(TokenValue::Empty), value(std::make_unique<TV_Empty>()) {}
explicit Token(const std::string &value) : type(TokenValue::String), value(std::make_unique<TV_String>(value)) {}
explicit Token(int value) : type(TokenValue::Int), value(std::make_unique<TV_Int>(value)) {}
explicit Token(float value) : type(TokenValue::Float), value(std::make_unique<TV_Float>(value)) {}
...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (*(tk1.value) == *(tk2.value)) ...
if (tk1.value->equals(tk2.value.get())) ...
...

However, what you are essentially doing is replicating what std::variant already is (a tagged union), so you should get rid of TokenValue completely and just use std::variant instead, eg:

struct EmptyToken {};

class Token
{
public:
enum class TokenType
{
Empty,
String,
Int,
Float
...;
};

std::variant<EmptyToken, std::string, int, float, ...> value;

explicit Token(const EmptyToken &value) : value(value) {}
explicit Token(const std::string &value) : value(value) {}
explicit Token(int value) : value(value) {}
explicit Token(float value) : value(value) {}
...

TokenType GetTokenType() const
{
static const TokenType types[] = {TokenType::Empty, TokenType::String, TokenType::Int, TokenType::Float, ...};
return types[value.index()];
};

...
};
Token tk1(std::string("test"));
Token tk2(12345);
if (tk1.value == tk2.value) ...
...

How to deal with base class methods that are incompatible with the derived class?

Let's step back and look at your design again:

class DataViewList {
using Col = std::string;
using Row = std::vector<std::string>;
virtual void addRow(Row) = 0;
virtual void deleteRow(Row) = 0;
virtual void addColumn(Col) = 0;
virtual void deleteColumn(Col) = 0;
virtual void draw(Context) = 0; // not in your question, but inferred
};

What you want to do is:

class MusicPlaylist : public DataViewList { /* ... */ }

And you discovered this cannot work, because MusicPlaylist does not fulfill the contract defined by DataViewList. Let's review this. What are the responsibilities of DataViewList?

  • It draws.
  • It provides a list of items.
  • It modifies that list of items.

That's 3 responsibilities. That violates 2 of the SOLID principles:

  • A class must have only one responsibility (Single-responsibility).
  • Consumer must not be forced to depend on things they do not use (Interface segregation).

And this is why you have a problem: MusicPlaylist only cares about 2 of those responsibilities, drawing and maintaining a list of items. It does not maintain a list of fields, and thus cannot inherit DataViewList.

How to fix?

Split the responsibilities.

// An interface for a data grid. Only provides a read-only view. No drawing.
struct DataList {
virtual const std::vector<Column>& getColumns() const = 0;
virtual const std::vector<Row>& getRows() const = 0;
protected:
~DataList(); // or make it public virtual to be able to delete a DataList*
};

// The widget that draws data.
class DataViewList : public Widget {
public:
virtual void setData(const DataList&); //could be a pointer, YMMV
virtual void draw(Context);
};

Note how:

  • DataViewList no longer contains any data, instead it will reference some other object that contains the data.
  • As it only shows the data, it depends on a simple interface that only contains reading functionnality.

At this point you can simply make a:

// Contains music data - does not draw it
class MusicPlaylist : public DataList {
void addSong(Song);
void deleteSong(Song);
const std::vector<Column>& getColumns() const override;
const std::vector<Row>& getRows() const override;
};

// Contains more complex data with configurable columns - does not draw it
class SomeMoreComplexList : public DataList {
void addColumn(Col);
void deleteColumn(Col);
void addRow(Row);
void deleteRow(Row);
const std::vector<Column>& getColumns() const override;
const std::vector<Row>& getRows() const override;
};

That is, your lists implement the interface that the widget requires to be able to display them, plus whatever specific functionality they need. You can then give them to the widget's setData.



Related Topics



Leave a reply



Submit