Чи покладається на неявне перетворення аргументів небезпечним?


10

У C ++ є функція (я не можу визначити її належну назву), яка автоматично викликає відповідні конструктори типів параметрів, якщо типи аргументів не є очікуваними.

Дуже простий приклад цього виклику функції, яка чекає на std::stringз const char*аргументом. Компілятор автоматично генерує код для виклику відповідного std::stringконструктора.

Мені цікаво, чи це так погано для читабельності, як я думаю, що це?

Ось приклад:

class Texture {
public:
    Texture(const std::string& imageFile);
};

class Renderer {
public:
    void Draw(const Texture& texture);
};

Renderer renderer;
std::string path = "foo.png";
renderer.Draw(path);

Це просто добре? Або це заходить занадто далеко? Якщо я не повинен цього робити, чи можу я якось змусити Кланг або GCC попередити про це?


1
що робити, якщо Draw був перевантажений версією рядка пізніше?
щурячий вирод

1
за відповідь @Dave Rager, я не думаю, що це складеться для всіх компіляторів. Дивіться мій коментар до його відповіді. Мабуть, згідно стандарту c ++, ви не можете ланцюжок неявних конверсій, як це. Ви можете зробити лише одну конверсію і не більше.
Джонатан Хенсон

Добре вибачте, насправді не компілював це. Оновлено приклад, і це все ще жахливо, IMO.
futlib

Відповіді:


24

Це називається конструктором, що перетворюється (або іноді неявний конструктор або неявне перетворення).

Мені невідомий перемикач часу компіляції, щоб попереджати, коли це відбувається, але це дуже легко запобігти; просто використовуйте explicitключове слово.

class Texture {
public:
    explicit Texture(const std::string& imageFile);
};

Що стосується того, чи є хороша ідея перетворення конструкторів: Це залежить.

Обставини, в яких неявна конверсія має сенс:

  • Клас досить дешевий, щоб побудувати, що вам все одно, якщо він неявно побудований.
  • Деякі класи концептуально схожі на свої аргументи (наприклад, що std::stringвідображають те саме поняття, яке const char *воно може неявно перетворити), тому неявна конверсія має сенс.
  • Деякі класи стають набагато неприємнішими для використання, якщо неявна конверсія вимкнена. (Подумайте про необхідність явно викликати std :: string кожного разу, коли ви хочете передати рядковий літерал. Частини Boost схожі.)

Обставини, при яких неявна конверсія має менше сенсу:

  • Будівництво дороге (наприклад, ваш приклад "Текстура", який вимагає завантаження та розбору графічного файлу).
  • Заняття концептуально дуже відрізняються від своїх аргументів. Розглянемо, наприклад, контейнер, схожий на масив, який приймає його розмір як аргумент:
    клас FlagList
    {
        FlagList (int Initial_size); 
    };

    недійсні SetFlags (const FlagList & flag_list);

    int main () {
        // Тепер це компілюється, хоча це зовсім не очевидно
        // що робить.
        SetFlags (42);
    }
  • Будівництво може мати небажані побічні ефекти. Наприклад, AnsiStringклас не повинен неявно будувати з а UnicodeString, оскільки перетворення Unicode в ANSI може втратити інформацію.

Подальше читання:


3

Це скоріше коментар, ніж відповідь, але занадто великий, щоб ставити коментар.

Цікаво, g++що мені це не дозволяють:

#include <iostream>
#include <string>

class Texture {
        public:
                Texture(const std::string& imageFile)
                {
                        std::cout << "Texture()" << std::endl;
                }
};

class Renderer {
        public:
                void Draw(const Texture& texture)
                {
                        std::cout << "Renderer.Draw()" << std::endl;
                }
};

int main(int argc, char* argv[])
{
        Renderer renderer;
        renderer.Draw("foo.png");

        return 0;
}

Випускає наступне:

$ g++ -o Conversion.exe Conversion.cpp 
Conversion.cpp: In function int main(int, char**)’:
Conversion.cpp:23:25: error: no matching function for call to Renderer::Draw(const char [8])’
Conversion.cpp:14:8: note: candidate is: void Renderer::Draw(const Texture&)

Однак якщо я зміню рядок на:

   renderer.Draw(std::string("foo.png"));

Він виконає конверсію.


Це справді цікава "особливість" в g ++. Я припускаю, що це або помилка, яка перевіряє лише один тип глибоко, замість того, щоб під час компіляції рекурсивно знижуватись якомога далі для створення правильного коду, або є прапор, який потрібно встановити у вашій команді g ++.
Джонатан Хенсон

1
en.cppreference.com/w/cpp/language/implicit_cast, здається, що g ++ дотримується стандарту досить суворо. Саме компілятор Microsoft або Mac є занадто щедрим з кодом ОП. Особливо важливим є твердження: "При розгляді аргументу конструктору або визначеній користувачем функції перетворення дозволена лише одна стандартна послідовність перетворень (інакше конверсії, визначені користувачем, можуть бути ефективно приковані)"
Джонатан Хенсон

Так, я просто кинув код разом, щоб перевірити деякі параметри gccкомпілятора (який, схоже, не існує для вирішення цього конкретного випадку). Я не надто розглядав це (я повинен працювати :-), але зважаючи gccна дотримання стандарту та використання explicitключового слова, варіант компілятора, ймовірно, вважався непотрібним.
Дейв Раджер

Неявні перетворення не пов'язані ланцюгом, і, Textureймовірно, не слід будувати неявно (відповідно до вказівок в інших відповідях), тому кращим сайтом для викликів був би renderer.Draw(Texture("foo.png"));(припускаючи, що він працює так, як я очікував).
Blaisorblade

3

Це називається неявною конверсією типу. Взагалі це добре, оскільки гальмує непотрібне повторення. Наприклад, ви автоматично отримуєте std::stringверсію Drawбез необхідності писати додатковий код для неї. Це також може допомогти в дотриманні принципу відкритого закриття, оскільки дозволяє розширити Rendererможливості, не змінюючи Rendererсебе.

З іншого боку, це не позбавлено недоліків. З одного боку, це може ускладнити з'ясування, звідки беруться аргументи. Іноді це може призвести до несподіваних результатів в інших випадках. Ось для чого це explicitключове слово. Якщо ви помістите його на Textureконструктор, він відключить використання цього конструктора для неявного перетворення типу. Мені невідомий метод глобального попередження про неявне перетворення типів, але це не означає, що метод не існує, лише те, що gcc має незрозуміло велику кількість варіантів.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.