I'm currently working on a college project with C++ and one of my assignments is to make a social network using inheritance and polymorphism. Currently I have a Node class that is used on a Map and Multimap (both are created manually and not used from the std). The node can hold two variables (key and data for example) and where I'm using it, the first variable can either be a pointer or a string (they let us use std::string).

The problem I'm having is that when I inherit from the "root" class (Object) and use "Object" as a data type for "key", I'm unable to pass a string created with the std as parameter to its constructor, because it doesn't inherit from my Object class. One solution is to implement my own string class and make it inherit from Object, but I was searching for other workarounds.

If there's any problem with the logic above, please tell me as I'm just beginning with C++.

EDIT 1 (some code for my Node):

class TempNode
{
    private:
    TempNode* next;
Key key;
T value;
public:
TempNode();
explicit TempNode(const Key thisKey, const T thisValue, TempNode* thisNext = NULL)
: key(thisKey)
, value(thisValue)
, next(thisNext)
{
}
inline Key getKey() { return key; }
inline T getValue() { return value; }
inline TempNode* getNext() { return next; }
inline void setNext(TempNode* thisNext) { next = thisNext; }
};

The string or Person types are currently used only in key, but that is with another implementation using templates (which works fine), but my teacher now requires us to apply inheritance to the entire project (to get used to it I guess).

有帮助吗?

解决方案

To implement this using inheritance, you think of Key as a data type specifically designed as a key in your map/multimap implementation. Key inherits from Object, but it may provide its own, key-specific functions, such as – for example – a function repr() which generates a representation used by the map for some map-specific operations (maybe as a basis for hashing, or sorting or whatever).

The map/multimap must be used in such a way that the Key objects are stored as pointers (or std::unique_ptr, or std::shared_ptr, or whatever is appropriate), but not as copies of Key objects.

So we have:

struct Object
{
  virtual ~Object()
  { }
};

/* Key class. Pointers of this type are inserted
   into the map. */    
class Key : public Object
{
public:
  /* Must be supported by all keys: */
  virtual std::string repr() const = 0;
};

We also assume there is a separate definition of Person objects:

struct Person : Object
{
  Person(const std::string &name)
    : name_(name)
  { }

  std::string name_;
};

According to your specification, there are two flavours of Key: One that represents strings and must be initialized using a string, and another one that represents persons and must be initialized by a person pointer (I'll assume that the person-keys do not actually own these pointers, so you need to make sure the person objects they point to stay alive as long as the person-key exists).

We implement this by specializing Key into two derived classes, a PersonKey and a StringKey:

class PersonKey : public Key
{
public:
  PersonKey(Person *person_ptr)
    : Key() , person_ptr_(person_ptr)
  { }

  virtual std::string repr() const
  {
    if (person_ptr_ != 0)
      return std::string("Person/") + person_ptr_->name_;
    else
      return "<NUL>";
  }

private:
  Person *person_ptr_;
};

class StringKey : public Key
{
public:
  StringKey(const std::string &str)
    : Key() , str_(str)
  { }

  virtual std::string repr() const
  {
    return str_;
  }

private:
  std::string str_;
};

When you make insertions into your map/multimap, you generate Key objects (which you represent as Key* or Key& or std::unique_ptr<Key>). When you want to insert a string, you generate them as StringKey objects, and when you want to insert them as person-pointers, you use PersonKey – but the data type of the key you insert will not reflect the specialization.

Here is an example of a general Key object (implemented as std::unique_ptr<Key>, but you may just use Key* if you are not afraid of memory leaks):

int main()
{
  /* General key object: */
  std::unique_ptr<Key> mykey;

  /* Now it points to a string-key, initialized using
     a string, as required: */
  mykey.reset(new StringKey("hello"));
  std::cout << "repr(mykey) == \""
            << mykey->repr()
            << '"'
            << std::endl;

  /* Now the same key object is made to refer to
     a person object: */
  Person person("me");
  mykey.reset(new PersonKey(&person));
  std::cout << "repr(mykey) == \""
            << mykey->repr()
            << '"'
            << std::endl;

  return 0;
}

Necessary headers for the code above are:

#include <iostream>
#include <memory>
#include <string>

(But memory is only required for my use of std::unique_ptr, which is not actually necessary to solve your problem.)

其他提示

I think what you are really looking for are templates. Your solution with "root object" won't work as you can see with standard objects and external libraries but also you will not be able to use your containers with primitives (for example person id(as int) as key, and Person class as value).

With templates you can say what type you are going to work with at compile time and compiler will help you to obey your own rules. It can be declared like this:

template<class T1, class T2>
class Map{
  T1 key;
  T2 value;
  (...)
}

Then you can use it more or less like this:

Map<std::String, int> phoneBook;

And compiler will guard you and warn, if you try to add, for example float instead of int, to you Map. But before you start coding I advice you to read some tutorials first, or maybe even some book on c++ in general. But if you want to start with generic right now, you can start her:

http://www.cplusplus.com/doc/tutorial/templates/

The only way you'd be able to store a string in your Object variable was if the string class inherited from your Object class, so you will have to implement your own String class unfortunately.

The real flaw here is that you are taking a Java/C# approach to design, where an Object variable can hold anything. In C++ the proper way to handle such things is through the use of templates, supposing your Map/Multimap/Node only need to hold one specific data type.

If your container needs to be able to hold any arbitrary data type, I would recommend using type erasure, although that can be a bit complicated for a beginner.

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top