Не используется static_cast?

У меня смешанные чувства по поводу static_cast, так как это самый безопасный листинг С++, но он позволяет одновременно безопасные и небезопасные конверсии, поэтому вы должны знать контекст, чтобы сказать, действительно ли он безопасен или может привести к UB (например, при передаче в подкласс).

Так почему же нет более безопасного явного броска? Вот пример, где это может быть полезно. В COM они должны вернуть указатель интерфейса как void** ppv, поэтому "нужно" явно указать

*ppv = (IInterface*) this;

который затем предложил заменить более безопасным С++-литом

*ppv = static_cast<IInterface*>(this);

Но имеет ли смысл сделать даже static_cast здесь? this имеет класс, который получается из IInterface, поэтому можно просто написать

IInterface* p = this; // implicit conversion to base, safe for sure
*ppv = p;

или используйте вспомогательный помощник, например

template<class T, class U>
T implicit_cast(U p) { return p; }

*ppv = implicit_cast<IInterface*>(this);

Итак, верно ли, что static_cast иногда используется неправильно и может (должно быть?) быть заменено этим implicit_cast в некоторых случаях, или я что-то не хватает?

EDIT: Я знаю, что требуется актерский состав в COM, но он не должен быть static_cast, неявного броска будет достаточно.

Ответы

Ответ 1

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

Похоже, что использование implicit_cast, возможно, будет более безопасным и позволит вам явно выбрать, какой базовый класс вы хотите неявно использовать (что, по-видимому, требуется для COM).

Я сделал быстрый тест с g++ и implicit_cast действительно возвращает разные адреса для разных базовых классов, как и ожидалось.

Обратите внимание, что в отношении вашего самого первого предложения я бы сказал, что dynamic_cast на самом деле безопаснее, чем static_cast, поскольку он возвращает null или throw, если приведение не может быть завершено. Напротив, static_cast вернет действительный указатель и позволит вам продолжать работу до тех пор, пока ваша программа не взорвется в какое-то время в будущем, не связанная с оригинальным плохим литом.

Программа тестирования:

#include <iostream>

class B1
{
public:
    virtual ~B1() {}
};

class B2
{
public:
    virtual ~B2() {}
};

class Foo : public B1, public B2
{
};

template<class T, class U>
T implicit_cast(U p) { return p; }

int main()
{
    Foo* f = new Foo;
    void **ppv = new void*;

    *ppv = implicit_cast<B1*>(f);
    std::cout << *ppv << std::endl;;
    *ppv = implicit_cast<B2*>(f);
    std::cout << *ppv << std::endl;;

    return 0;
}