Как создать производные классы из базового класса с помощью шаблонного программирования на C++?

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

Например, мой базовый класс определяется как:

class BaseError : public std::exception
{
private:
    int osErrorCode;
    const std::string errorMsg;

public:
    int ec;
    BaseError () : std::exception(), errorMsg() {}

    BaseError (int errorCode, int osErrCode, const std::string& msg)
         : std::exception(), errorMsg(msg)
    {
       ec = errorCode;
       osErrorCode = osErrCode;
    }

    BaseError (const BaseError& other)
        : std::exception(other), errorMsg(other.errorMsg)
    {
        ec  = other.errorCode;
        osErrorCode = other.osErrorCode;
    }

    const std::string& errorMessage() const { return errorMsg; }

    virtual ~BaseError() throw(){}

}

Мне нужно создать множество производных классов от этого базового класса, каждый из которых имеет свои собственные конструкторы, конструктор копирования и функцию виртуального деструктора, в настоящее время я копирую/вставляю код, меняя имена там, где это необходимо:

class FileError : public BaseError{
private:
    const std::string error_msg;

public:
    FileError () :BaseError(), error_msg() {}

    FileError (int errorCode, int osErrorCode, const std::string& errorMessage)
        :BaseError(errorCode, osErrorCode, errorMessage){}

    virtual ~FileError() throw(){}
};

Вопрос. Есть ли способ создать эти классы с использованием шаблонов, чтобы реализация не повторялась?


person sarshad    schedule 13.12.2010    source источник
comment
Немного не связанный комментарий: вместо того, чтобы предоставлять свой собственный геттер const std::string& errorMessage() const, вы можете переопределить виртуальную функцию const char *std::exception::what() const, которую вы получаете, наследуя std::exception.   -  person Frerich Raabe    schedule 13.12.2010
comment
зачем вам производные классы? Разве простого typedef недостаточно? Насколько я вижу, в производных классах нет ничего полезного.   -  person Naveen    schedule 13.12.2010
comment
Если поведение ваших производных классов останется прежним, почему бы не использовать шаблонный класс?   -  person Arunmu    schedule 13.12.2010
comment
Еще один отдаленно связанный комментарий: если все ваши исключения (могут) иметь сообщение, рассмотрите возможность наличия одного члена std::string errorMsg; в вашем базовом классе вместо одного в базовом классе, а затем по одному на производный класс. Это немного более эффективно (с точки зрения памяти и скорости выполнения). Вы можете установить строковую переменную-член, предоставив (защищенный) метод установки или передав аргумент конструктору базового класса.   -  person Frerich Raabe    schedule 13.12.2010


Ответы (3)


Я предполагаю, что вы хотите создать иерархию классов, чтобы вы могли использовать динамическую диспетчеризацию в своих предложениях catch (полагаясь на то, что компилятор определит правильный тип) для реализации пользовательской обработки ошибок. Вы можете сделать это, сохранив класс BaseError как есть, а затем добавив класс-шаблон, для которого вы затем предоставите несколько экземпляров. Учти это:

class BaseError : public std::exception
{
private:
    int osErrorCode;
    const std::string errorMsg;

public:
    int ec;
    BaseError () : std::exception(), errorMsg() {}

    BaseError (int errorCode, int osErrCode, const std::string& msg)
         : std::exception(), errorMsg(msg)
    {
       ec = errorCode;
       osErrorCode = osErrCode;
    }

    // ...
};

template <int T>
class ConcreteError : public BaseError {
public:
    ConcreteError () :BaseError(), error_msg() {}

    ConcreteError (int errorCode, int osErrorCode, const std::string& errorMessage)
        :BaseError(errorCode, osErrorCode, errorMessage){}
};

Теперь вы можете настроить несколько определений типов:

typedef ConcreteError<0> FileError;
typedef ConcreteError<1> NetworkError;
typedef ConcreteError<2> DatabaseError;
// ...

Теперь у вас есть иерархия с тремя различными классами ошибок.

person Frerich Raabe    schedule 13.12.2010
comment
Мне это нравится, очень элегантно (особенно то, что вы всегда позже даете конкретным исключениям свою собственную реализацию, не нарушая этой схемы). Единственное, что нужно добавить, это то, что целые значения должны быть либо именованными константами, либо частью перечисления. :) - person Mephane; 13.12.2010
comment
Позволяет ли это создавать исключения из общей библиотеки? - person Alexandre C.; 13.12.2010
comment
@Mephane: Что в этом случае дадут вам именованные константы или перечисление? Вам просто нужно иметь отдельные классы - вы никогда не будете ссылаться на них по идентификатору, поэтому вам не нужны имена для идентификаторов. - person Frerich Raabe; 13.12.2010
comment
Ну, вы могли бы иметь перечисление, например. { FILE, NETWORK, DATABASE, ... } и переименуйте ConcreteError просто в Error и пропустите typedefs в пользу простого использования, например. Error<FILE>. :) - person Karl Knechtel; 13.12.2010
comment
@Frerich Raabe: это действительно самое элегантное решение. У меня только один вопрос, должны ли идентификационные номера быть уникальными для typedefs? Или есть ли какая-то разница, если я просто использую один и тот же номер для всех из них? Кроме того, большое спасибо за подробное рассмотрение моего кода. Обязательно поработаю над вашими предложениями. - person sarshad; 13.12.2010
comment
@sarshad: Да, числа должны быть уникальными. Совершенно неважно, какие числа вы выберете, с таким же успехом вы можете использовать -3, 19 и 4711 вместо 0, 1, 2. Каждое число создает новый класс. - person Frerich Raabe; 13.12.2010

Если реализации идентичны, сделайте перечисление и шаблон на нем.

enum error {
    file_error,
};
template<error e> class my_exception : public BaseError {
    ....
};
typedef my_exception<file_error> file_exception;
person Puppy    schedule 13.12.2010
comment
Зачем в этом случае использовать именованные константы или перечисление? Вам просто нужно иметь отдельные классы - вы никогда не будете ссылаться на них по идентификатору, поэтому вам не нужны имена для идентификаторов. Вы всегда используете только typedefs. - person Frerich Raabe; 13.12.2010
comment
@Frerich: нет ничего плохого в том, чтобы ссылаться на них перечислением. Это дополнительное преимущество для этого решения. - person Puppy; 13.12.2010

Если реализация точно такая же, и вам просто нужно другое имя для каждого класса, тогда простой typedef сделает вашу работу.

И если есть небольшая разница в реализации, а не в интерфейсе, возможно, вам понадобятся шаблоны. Затем рассмотрите также дизайн, основанный на политике.

person Nawaz    schedule 13.12.2010