Обеспечение безопасности auto_cast
GMan опубликовал код для delicious auto_cast
"operator" , который позволяет писать код, например, следующий в С++:
float f = 4.0f;
int i = auto_cast(f);
// instead of:
int j = static_cast<int>(f);
или, более ярко,
T x = value;
typename nested_type<with, template_arguments>::type y = auto_cast(x);
// instead of
typedef typename nested_type<with, template_arguments>::type my_type;
my_type z = static_cast<my_type>(x);
В принципе, оператор отлично справляется с удалением ненужной избыточности из static_cast
и в то же время по-прежнему безопасен. Его еще более безопасно, чем static_cast
, поскольку он предотвращает случайное несоответствие типов:
int i = 1234;
short s = static_cast<char>(i); // s == -46, not 1234!
Однако j_random_hacker заметил недостаток оператора:
static_cast позволяет downcasts, которые являются потенциально опасными.
Действительно, auto_cast
должен, вероятно, запрещать downcasts, потому что они могут выйти из строя:
class base { };
class derived : public base { };
base b;
derived* pd = auto_cast(&b); // should fail at compile time.
Отсюда мой вопрос:
Как бы вы изменили реализацию auto_cast
, чтобы запретить downcasts?
Вероятно, это будет enable_if
. Мне особенно интересно решение, позволяющее компилятору обеспечить хорошую диагностику в случае сбоя (= читаемые сообщения об ошибках).
Ответы
Ответ 1
Кажется, вы хотите использовать форму инициализации T{u}
.
template <typename U>
operator U()
{
return U{std::forward<T>(mX)};
}
Одна из причин такой равномерной инициализации заключалась в том, что для использования явных конструкторов для создания временного, вам нужно сделать aka T(u)
. С T{u}
эта проблема была решена. Для С++ 03, я думаю, вы могли бы сделать что-то вроде этого:
template<typename T>
struct construct_explicit {
template<typename U>
construct_explicit(U &u):t(u) { }
template<typename U>
construct_explicit(U const &u):t(u) { }
T &get() { return t; }
T const& get() const { return t; }
T t;
};
Тогда вы можете сказать construct_explicit<U>(mX).get()
, хотя в случае, например, в вашей функции преобразования, он также работает, чтобы использовать именованную переменную в качестве промежуточного шага, я думаю,
template <typename U>
operator U()
{
// or C++03: U u(mX);
U u(std::forward<T>(mX));
return u;
}
Ответ 2
Вы можете использовать признаки типа, чтобы отключить оператор, если T
является базой R
. Поскольку мы находимся в С++ 0x, вы можете явно static_assert(std::is_base_of<T, U>::value, "Cannot auto_cast downwards!");
Ответ 3
Я бы даже не использовал auto_cast, потому что static_cast, const_cast, dynamic_cast и reinterpret_cast также сделаны уродливыми по дизайну, чтобы помочь указывать код, который может потребоваться рефакторинг: уродливая операция должна иметь уродливый вид.
Вторая причина для введения стиль нового стиля заключался в том, что C-style casts очень сложно обнаружить в программе. Например, вы не можете удобно поиск отливок с использованием обычного редактор или текстовый редактор. Эта почти невидимость приведения в стиле C особенно несчастны, потому что они настолько потенциально опасны. Уродливые операция должна быть уродливой синтаксическая форма. Это наблюдение было часть причины выбора синтаксис для приведения нового стиля. дальнейшая причина заключалась в новом стиле бросает, чтобы соответствовать нотации шаблона, так что программисты могут писать свои собственные броски, особенно проверенные временем выполнения слепки.
http://www2.research.att.com/~bs/bs_faq2.html#static-cast
Я предпочитаю четко видеть код, где он может быть лучше, или где нам явно нужно сделать эту уродливую операцию.