Реализация именованных аргументов в C++ с производными классами
Я пытаюсь создать конструктор, подобный именованным аргументам, для некоторых классов проекта.
Я делаю это, определяя прокси класса, который будет содержать аргументы и передавать экземпляр этого прокси конструктору моих классов.
Все работало нормально, пока мне не пришлось получить один из моих классов.
По сути, я подумал: я собираюсь получить новый прокси производного класса от прокси базового класса. Это тоже работает, но только если я использую только аргументы производного класса прокси.
Вот пример, так как это легче понять:
class Person
{
public:
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
PersonArgs& Name(const std::string& name)
{
_name = name;
return *this;
}
const std::string& Surname() const { return _surname; }
PersonArgs& Surname(const std::string& surname)
{
_surname = surname;
return *this;
}
protected:
std::string _name;
std::string _surname;
}
public:
Person()
: _name("")
, _surname("")
{ }
Person(const PersonArgs& args)
: _name(args.Name())
, _surname(args.Surname())
{ }
protected:
std::string _name;
std::string _surname;
}
class PersonEx : public Person
{
public:
class PersonExArgs : public Person::PersonArgs
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
protected:
std::string _address;
}
public:
PersonEx()
: _address("")
{ }
PersonEx(const PersonExArgs& args)
: Person(args)
, _address(args.Address())
{ }
protected:
std::string _address;
}
int main(int argc, char** argv)
{
// This is ok since PersonExArgs::Address returns a PersonExArgs&
PersonEx* p1 = new PersonEx(PersonEx::PersonExArgs().Address("example"));
// This won't work since PersonExArgs::Name returns a PersonArgs&
PersonEx* p2 = new PersonEx(PersonEx::PersonExArgs().Address("example").Name("Mark"));
}
По сути, поскольку я приковываю аргументы, возвращающие ссылку на экземпляр прокси-класса, когда я устанавливаю аргумент, он прерывается при использовании этого из производного прокси-класса, так как он возвращает ссылку на базовый прокси-класс, а не на производный, что не позволяет мне получить доступ к производным аргументам прокси и не передавать его конструктору производного класса.
У кого-нибудь есть идея, как это исправить?
2 ответа
Возможно, вы ищете ковариантные типы возврата.
Смотрите здесь для более подробной информации.
Вы можете определить PersonArgs
как (примечание virtual
ключевые слова расположены вокруг):
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
virtual PersonArgs& Name(const std::string& name)
{
_name = name;
return *this;
}
const std::string& Surname() const { return _surname; }
virtual PersonArgs& Surname(const std::string& surname)
{
_surname = surname;
return *this;
}
protected:
std::string _name;
std::string _surname;
};
Затем определите PersonExArgs
как (примечание override
и ковариантный тип возврата):
class PersonExArgs : public Person::PersonArgs
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
PersonExArgs& Name(const std::string& name) override
{
PersonArgs::Name(name);
return *this;
}
PersonExArgs& Surname(const std::string& surname) override
{
PersonArgs::Surname(surname);
return *this;
}
protected:
std::string _address;
};
Вероятно, это раздражает, потому что вам приходится переопределять каждую функцию в базовом классе, но это хорошо работает.
Посмотрите это и работает на Wandbox.
Наиболее распространенным решением этой проблемы является шаблон CURLY Recurring Template Pattern (CRTP):
template <typename Derived>
class PersonArgs
{
public:
const std::string& Name() const { return _name; }
Derived& Name(const std::string& name)
{
_name = name;
return static_cast<Derived&>(*this);
}
const std::string& Surname() const { return _surname; }
Derived& Surname(const std::string& surname)
{
_surname = surname;
return static_cast<Derived&>(*this);
}
protected:
std::string _name;
std::string _surname;
};
...
class PersonExArgs : public Person::PersonArgs<PersonExArgs>
{
public:
const std::string& Address() const { return _address; }
PersonExArgs& Address(const std::string& address)
{
_address = address;
return *this;
}
protected:
std::string _address;
};
В вашем случае вы можете объединить его с другим базовым классом для очистки интерфейса:
class Person {
class PersonArgsBase
{
public:
const std::string& Name() const { return _name; }
const std::string& Surname() const { return _surname; }
protected:
std::string _name;
std::string _surname;
};
template <typename Derived>
class PersonArgs : public PersonArgsBase
{
Derived& Name(const std::string& name)
{
_name = name;
return static_cast<Derived&>(*this);
}
Derived& Surname(const std::string& surname)
{
_surname = surname;
return static_cast<Derived&>(*this);
}
};
...
};
class PersonEx {
class PersonExArgs : public Person::PersonArgs<PersonExArgs>
{
...
};
};