можно изменить тип возвращаемого значения при переопределении виртуальной функции в C ++? - PullRequest
7 голосов
/ 19 ноября 2010

Я сталкиваюсь с проблемой переопределения виртуальных функций, фактически речь идет о гессиане (протокол веб-службы).

имеет базовый класс Object и некоторые производные классы: Long, Int, String,..., все производные классы имеют не-виртуальную функцию "value"

   class Object  
   {  
     ...    
   };  


   class Long :public Object  
   {  
       ...  
   public:  
       typedef long long  basic_type;  
       basic_type value(){return value_;}  
   private:  
       basic_type value_;  
       ...  
   };  


   class Int :public Object  
   {  
      ...  
   public:  
       typedef int basic_type;  
       basic_type value(){return value_;}  
   private:   
       basic_type value_;  
       ...  
   };  

, теперь я хочу добавить функцию, скажем, toString, которая может преобразовывать Object в строку:

Object *obj  = ...
cout<<obj->toString();

Если я могу изменить функцию-значение на виртуальную, мне нужно только написать функцию toString в Object, в противном случае мне нужно написать виртуальную функцию toString и переопределить эту функцию во всех производных классах.

например

   class Object  
   {  
       virtual Type value(); // It seemed that I can't write a function like this,because the Type is different for different derived classes  


       std::string toString()  
       {  
           some_convert_function(value());  
       }  

   };  

, но я не могу написать функцию виртуального значения, поскольку возвращаемое значение не может быть переопределено.

Есть ли хорошее решение для этой проблемы?

Спасибо

Ответы [ 6 ]

8 голосов
/ 19 ноября 2010

можно изменить тип возвращаемого значения при переопределении виртуальной функции в C ++?

Только очень ограниченным образом, в этом (необработанном) указателе или ссылочном типе возврата может быть ковариантным.

Есть ли хорошее решение для этой проблемы?

Ну, есть два довольно хороших решения и одно немного плохое решение.

Я даю вам немного плохое решение здесь. Одна из причин, которую я привел, заключается в том, что это легко понять или, по крайней мере, довольно легко «копировать и изменять», даже если кто-то не совсем понимает это. Другая причина состоит в том, что одно из хороших решений требует некоторого обширного механизма общей поддержки, который здесь не может обсуждаться, а другое хорошее решение (которое, на мой взгляд, является наилучшим почти во всех отношениях), такого рода, по крайней мере, когда я представлял такое решение, он автоматически получал понижающие голоса и только это, здесь, на SO. Я полагаю, что это цена, которую нужно заплатить за разнообразие здесь, и это очень хорошая вещь :-) Но, к сожалению, это означает, что нет смысла предлагать действительно хорошие вещи, тогда я был бы до отрицательного представителя.

В любом случае, код, основанный на доминировании в виртуальном наследовании; это примерно то же самое, что наследование реализации интерфейса в Java или C #:

#include <iostream>
#include <string>
#include <sstream>

//--------------------------------------- Machinery:

class ToStringInterface
{
public:
    virtual std::string toString() const = 0;
};

template< typename ValueProvider >
class ToStringImpl
    : public virtual ToStringInterface
{
public:
    virtual std::string toString() const
    {
        ValueProvider const&    self    =
            *static_cast<ValueProvider const*>( this );
        std::ostringstream      stream;
        stream << self.value();
        return stream.str();
    }
};

//--------------------------------------- Usage example:

class Object  
    : public virtual ToStringInterface
{  
    // ...    
};  

class Long
    : public Object
    , public ToStringImpl< Long >
{  
public:  
   typedef long long  BasicType;  
   Long( BasicType v ): value_( v ) {}
   BasicType value() const { return value_; }  
private:  
   BasicType value_;  
};  

class Int
    : public Object
    , public ToStringImpl< Int >
{  
public:  
   typedef int BasicType;  
   Int( BasicType v ): value_( v ) {}
   BasicType value() const { return value_; }
private:   
   BasicType value_;  
}; 

int main()
{
    Object const& obj = Int( 42 );
    std::cout << obj.toString() << std::endl;
}

Если ваши Long и Int классы и т. Д. Очень похожи, как кажется, рассмотрите возможность определения только одного шаблона класса или, возможно, унаследуйте от специализаций такого шаблона (это также может помочь избежать ошибок, так как это уменьшает избыточность).

РЕДАКТИРОВАТЬ : Теперь я вижу, что вы приняли ответ, который по сути является лишь моим последним предложением о шаблонировании. Это означает, что я ответил на поставленный вопрос (решение для разных, разных классов), а вы имели в виду нечто более общее. Ну хорошо.

Приветствия & hth.,

2 голосов
/ 19 ноября 2010

Нет, вы не можете записать toString в Object, используя виртуальную функцию 'value', и переопределить тип возвращаемого значения.Однако вы можете написать виртуальную строку ToString и с помощью трюка программирования шаблонов выполнить почти то же самое.

class Object
{
public:
  virtual std::string toString();
}


template < class ValueType >
class BasicType : Object
{
  public:
  typedef ValueType basic_type;
  basic_type value() { return value_; }

  std::string toString()
  {
    return some_convert_function( value_ );
  }

  private:
  basic_type value_;
}

typedef BasicType<long long> Long;
typedef BasicType<int>       Int;
1 голос
/ 19 ноября 2010

К сожалению, вы не можете перегружать функции в C ++ возвращаемым значением.Что вы могли бы сделать, если у вас есть соответствующий some_convert_function для всех типов, для которых он нужен, - это бесплатно создать шаблонную функцию, которая выглядит примерно так:

template<typename T>
std::string toString(T const& t)
{
  return some_convert_function<T>(t);
}
0 голосов
/ 07 августа 2014

Что касается комментария @MerickOWA, вот еще одно решение, которое не требует какого-либо дополнительного механизма шаблонов.

Поскольку вы намеревались иметь виртуальный метод "value ()", который вам нужно было реализовать во всех классах, я расширил идею (обычно в подобных средах у вас есть множество похожих "базовых" методов). , поэтому я использовал макрос, чтобы написать их для меня, он не требуется, он просто быстрее и менее подвержен ошибкам.

#include <iostream>
#include <string>
#include <sstream>

struct Object
{
   std::string toString() const { std::ostringstream str; getValue(str); return str.str(); }
   virtual void getValue(std::ostringstream & str) const { str<<"BadObj"; }
};

// Add all the common "basic & common" function here
#define __BoilerPlate__     basic_type value; void getValue(std::ostringstream & str) const { str << value; }
// The only type specific part
#define MAKE_OBJ(T)         typedef T basic_type;      __BoilerPlate__

struct Long : public Object
{
   MAKE_OBJ(long long)
   Long() : value(345) {}
};

struct Int : public Object
{
   MAKE_OBJ(long)
   Int() : value(3) {}
};

int main()
{
    Object a;
    Long b;
    Int c;
    std::cout<<a.toString()<<std::endl; // BadObj
    std::cout<<b.toString()<<std::endl; // 345
    std::cout<<c.toString()<<std::endl; // 3
    return 0;
}

Очевидно, что хитрость заключается в классах std :: ostringstream, которые принимают любой тип параметра (long long, long и т. Д.). Поскольку это стандартная практика C ++, это не должно иметь значения.

0 голосов
/ 19 ноября 2010

Я не думаю, что вы делаете это правильно. Хотя в некоторых случаях возможно изменить тип возвращаемого значения виртуальной функции, рассмотрим следующее: как используется ваша функция? Если это виртуально, изменения в том, что пользователи будут использовать базовый класс. Как таковые, они не обращают внимания на то, каков реальный тип вашего класса, и, таким образом, они не будут знать, какой тип ожидать. Итак:

  • Либо вернуть тип базового класса.
  • Вернуть функции, которые дают вам правильный тип (т.е. virtual std::string getStringValue(), который дает вам строку, если применимо).
  • Используйте шаблоны, если тип известен пользователю.
0 голосов
/ 19 ноября 2010

Вы не можете переопределить функцию с другим типом возврата;самое близкое, что вы можете придумать - это скрыть функцию в родительском элементе с другой функцией в производном классе.Но это не то, что вы хотите, потому что эти две функции будут разными, совершенно не связанными.

Вы были правы, предполагая, что вам нужно будет создать новую функцию toString в каждом производном классе - это и есть полиморфизмэто все о.

...