การใช้การแปลงอาร์กิวเมนต์โดยนัยถือว่าเป็นอันตรายหรือไม่


10

C ++ มีคุณสมบัติ (ฉันไม่สามารถหาชื่อที่ถูกต้องได้) ซึ่งเรียกการสร้างพารามิเตอร์ที่ตรงกันโดยอัตโนมัติหากประเภทอาร์กิวเมนต์ไม่ใช่ประเภทที่คาดไว้

ตัวอย่างพื้นฐานของสิ่งนี้คือการเรียกใช้ฟังก์ชันที่คาดว่าจะstd::stringมีconst char*อาร์กิวเมนต์ คอมไพเลอร์จะสร้างรหัสโดยอัตโนมัติเพื่อเรียกใช้std::stringConstructor ที่เหมาะสม

ฉันสงสัยว่ามันดีสำหรับการอ่านอย่างที่คิดหรือไม่?

นี่คือตัวอย่าง:

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);

ไม่เป็นไรใช่ไหม หรือมันไปไกลเกินไป? ถ้าฉันไม่ควรทำฉันจะทำให้ Clang หรือ GCC เตือนได้ไหม?


1
จะเกิดอะไรขึ้นถ้า Draw ถูกโอเวอร์โหลดด้วยรุ่นสตริงในภายหลัง
วงล้อประหลาด

1
ตามคำตอบของ @Dave Rager ฉันไม่คิดว่าจะรวบรวมคอมไพเลอร์ทั้งหมด ดูความคิดเห็นของฉันในคำตอบของเขา เห็นได้ชัดว่าตามมาตรฐาน c ++ คุณไม่สามารถโยงการแปลงโดยนัยเช่นนี้ได้ คุณสามารถแปลงได้เพียงหนึ่งรายการเท่านั้นและไม่เกิน
Jonathan Henson

ตกลงขออภัยไม่ได้รวบรวมสิ่งนี้จริงๆ อัปเดตตัวอย่างและมันก็ยังน่ากลัว IMO
futlib

คำตอบ:


24

สิ่งนี้เรียกว่าคอนสตรัคเตอร์การแปลง (หรือบางครั้งคอนสตรัคเตอร์หรือการแปลงโดยปริยาย)

ฉันไม่ได้ตระหนักถึงการสลับเวลารวบรวมเพื่อเตือนเมื่อเกิดเหตุการณ์นี้ แต่มันง่ายมากที่จะป้องกัน; เพียงใช้explicitคำหลัก

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

ว่าการแปลงคอนสตรัคเตอร์เป็นความคิดที่ดีหรือไม่นั้นขึ้นอยู่กับว่า

สถานการณ์ที่การแปลงโดยปริยายมีเหตุผล:

  • ชั้นเรียนมีราคาถูกพอที่จะสร้างได้โดยที่คุณไม่สนใจว่ามันถูกสร้างขึ้นโดยปริยายหรือไม่
  • บางคลาสคล้ายกับแนวคิดของอาร์กิวเมนต์ (เช่นstd::stringสะท้อนแนวคิดเดียวกันกับที่const char *มันสามารถแปลงได้โดยปริยาย) ดังนั้นการแปลงโดยนัยจึงสมเหตุสมผล
  • บางคลาสกลายเป็นสิ่งที่ไม่พึงประสงค์หากใช้การแปลงโดยปริยาย (คิดว่าต้องเรียกใช้ std :: string ทุกครั้งที่คุณต้องการส่งผ่านตัวอักษรทุกส่วนของ Boost มีความคล้ายคลึงกัน)

สถานการณ์ที่การแปลงโดยนัยมีความหมายน้อยกว่า:

  • การก่อสร้างมีราคาแพง (เช่นตัวอย่างพื้นผิวของคุณซึ่งต้องโหลดและแยกไฟล์กราฟิก)
  • ชั้นเรียนมีแนวคิดที่แตกต่างกันมากในการโต้แย้ง ลองพิจารณาเช่น container-like array ที่มีขนาดเป็นอาร์กิวเมนต์:
    FlagList ระดับ
    {
        FlagList (int initial_size); 
    };

    เป็นโมฆะ SetFlags (const FlagList & flag_list);

    int main () {
        // ตอนนี้คอมไพล์ถึงแม้ว่ามันจะไม่ชัดเจน
        // มันทำอะไรอยู่
        SetFlags (42);
    }
  • การก่อสร้างอาจมีผลข้างเคียงที่ไม่พึงประสงค์ ตัวอย่างเช่นAnsiStringคลาสไม่ควรสร้างโดยปริยายจาก a 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 ++ ของคุณ
Jonathan Henson

1
en.cppreference.com/w/cpp/language/implicit_castดูเหมือนว่า g ++ จะปฏิบัติตามมาตรฐานค่อนข้างเคร่งครัด มันเป็นคอมไพเลอร์ของ Microsoft หรือ Mac ที่ค่อนข้างใจกว้างเกินไปกับรหัสของ OP โดยเฉพาะอย่างยิ่งการบอกคือข้อความว่า: "เมื่อพิจารณาข้อโต้แย้งกับตัวสร้างหรือฟังก์ชันการแปลงที่ผู้ใช้กำหนดอนุญาตให้ใช้ลำดับการแปลงมาตรฐานเพียงลำดับเดียวเท่านั้น (มิฉะนั้นการแปลงที่ผู้ใช้กำหนดเองอาจถูกล่ามโซ่ได้อย่างมีประสิทธิภาพ)"
Jonathan Henson

ใช่ฉันเพิ่งโยนรหัสเข้าด้วยกันเพื่อทดสอบgccตัวเลือกคอมไพเลอร์บางตัว (ซึ่งดูเหมือนว่าจะไม่มีที่อยู่ในกรณีนี้โดยเฉพาะ) ฉันไม่ได้มองไกลไปกว่านี้อีกแล้ว (ฉันควรจะทำงาน :-) แต่ได้รับการgccปฏิบัติตามมาตรฐานและการใช้explicitคำหลักตัวเลือกคอมไพเลอร์อาจถือว่าไม่จำเป็น
Dave Rager

การแปลงโดยนัยไม่ได้ถูกผูกมัดไว้และ a Textureไม่น่าจะถูกสร้างขึ้นโดยปริยาย (ตามแนวทางในคำตอบอื่น ๆ ) ดังนั้นไซต์การโทรที่ดีกว่าก็renderer.Draw(Texture("foo.png"));คือ
Blaisorblade

3

มันเรียกว่าการแปลงประเภทโดยนัย โดยทั่วไปมันเป็นสิ่งที่ดีเพราะมันยับยั้งการทำซ้ำที่ไม่จำเป็น ตัวอย่างเช่นคุณจะได้รับstd::stringเวอร์ชันโดยอัตโนมัติDrawโดยไม่ต้องเขียนโค้ดพิเศษใด ๆ นอกจากนี้ยังสามารถช่วยในการปฏิบัติตามหลักการแบบเปิดซึ่งจะช่วยให้คุณสามารถขยายRendererความสามารถของตนได้โดยไม่ต้องดัดแปลงRendererตัวเอง

ในทางกลับกันก็ไม่ได้ไม่มีข้อเสีย มันสามารถทำให้ยากที่จะคิดออกว่าอาร์กิวเมนต์มาจากไหนสำหรับสิ่งหนึ่ง บางครั้งสามารถให้ผลลัพธ์ที่ไม่คาดคิดในกรณีอื่น นั่นคือสิ่งที่เป็นexplicitคำหลักสำหรับ หากคุณวางไว้บนTextureนวกรรมิกมันจะปิดการใช้งานคอนสตรัคเตอร์นั้นสำหรับการแปลงชนิดโดยนัย ฉันไม่ทราบวิธีการเตือนทั่วโลกเกี่ยวกับการแปลงประเภทโดยนัย แต่นั่นไม่ได้หมายความว่าไม่มีวิธีการเฉพาะ gcc นั้นมีตัวเลือกจำนวนมากอย่างไม่สามารถเข้าใจได้

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.