Là dựa vào chuyển đổi đối số ngầm được coi là nguy hiểm?


10

C ++ có một tính năng (tôi không thể tìm ra tên riêng của nó), nó tự động gọi các hàm tạo khớp của các loại tham số nếu các loại đối số không phải là các đối số dự kiến.

Một ví dụ rất cơ bản về điều này là gọi một hàm mong đợi std::stringmột const char*đối số. Trình biên dịch sẽ tự động tạo mã để gọi hàm tạo thích hợp std::string.

Tôi tự hỏi, nó có tệ cho khả năng đọc như tôi nghĩ không?

Đây là một ví dụ:

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

Điều đó có tốt không? Hay nó đi quá xa? Nếu tôi không nên làm điều đó, bằng cách nào đó tôi có thể khiến Clang hoặc GCC cảnh báo về điều đó không?


1
Điều gì xảy ra nếu Draw bị quá tải với phiên bản chuỗi sau này?
ratchet freak

1
theo câu trả lời của @Dave Reller, tôi không nghĩ rằng điều này sẽ biên dịch trên tất cả các trình biên dịch. Xem bình luận của tôi về câu trả lời của anh ấy. Rõ ràng, theo tiêu chuẩn c ++, bạn không thể chuỗi các chuyển đổi ngầm định như thế này. Bạn chỉ có thể thực hiện một chuyển đổi và không hơn.
Jonathan Henson

OK xin lỗi, đã không thực sự biên dịch này. Đã cập nhật ví dụ và nó vẫn còn khủng khiếp, IMO.
futlib

Câu trả lời:


24

Điều này được gọi là một hàm tạo chuyển đổi (hoặc đôi khi là hàm tạo ẩn hoặc chuyển đổi ẩn).

Tôi không biết về việc chuyển đổi thời gian biên dịch để cảnh báo khi điều này xảy ra, nhưng nó rất dễ ngăn chặn; chỉ cần sử dụng explicittừ khóa.

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

Về việc có hay không chuyển đổi các nhà xây dựng là một ý tưởng tốt: Nó phụ thuộc.

Hoàn cảnh trong đó chuyển đổi ngầm định có ý nghĩa:

  • Lớp này đủ rẻ để xây dựng mà bạn không quan tâm nếu nó được xây dựng ngầm.
  • Một số lớp tương tự về mặt khái niệm với các đối số của chúng (chẳng hạn như std::stringphản ánh khái niệm tương tự như const char *nó có thể chuyển đổi hoàn toàn từ đó), vì vậy chuyển đổi ngầm có ý nghĩa.
  • Một số lớp trở nên khó sử dụng hơn rất nhiều nếu chuyển đổi ngầm bị vô hiệu hóa. (Hãy nghĩ đến việc phải gọi std :: chuỗi một cách rõ ràng mỗi khi bạn muốn vượt qua một chuỗi ký tự. Các phần của Boost cũng tương tự.)

Các trường hợp trong đó chuyển đổi ngầm định có ý nghĩa ít hơn:

  • Xây dựng là tốn kém (chẳng hạn như ví dụ Texture của bạn, yêu cầu tải và phân tích tệp đồ họa).
  • Các lớp học về mặt khái niệm rất giống với lập luận của họ. Ví dụ, hãy xem xét một thùng chứa giống như mảng lấy kích thước của nó làm đối số:
    lớp FlagList
    {
        FlagList (int init_size); 
    };

    void SetFlags (const FlagList & flag_list);

    int chính () {
        // Bây giờ, nó biên dịch, mặc dù nó hoàn toàn không rõ ràng
        // nó đang làm gì
        SetFlags (42);
    }
  • Xây dựng có thể có tác dụng phụ không mong muốn. Ví dụ, một AnsiStringlớp không nên ngầm định xây dựng từ một UnicodeString, vì chuyển đổi Unicode sang ANSI có thể mất thông tin.

Đọc thêm:


3

Đây là một nhận xét nhiều hơn là một câu trả lời nhưng quá lớn để đưa vào một nhận xét.

Thật thú vị, g++không để tôi làm điều đó:

#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;
}

Sản xuất như sau:

$ 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&)

Tuy nhiên, nếu tôi thay đổi dòng thành:

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

Nó sẽ thực hiện chuyển đổi đó.


Đó thực sự là một "tính năng" thú vị trong g ++. Tôi cho rằng đó là một lỗi chỉ kiểm tra sâu một loại thay vì đệ quy xuống càng xa càng tốt trong thời gian biên dịch để tạo mã chính xác hoặc có một cờ cần được đặt trong lệnh g ++ của bạn.
Jonathan Henson

1
en.cppreference.com/w/cpp/lingu/implicit_cast có vẻ như g ++ đang tuân theo tiêu chuẩn khá nghiêm ngặt. Trình biên dịch của Microsoft hoặc Mac quá hào phóng với mã của OP. Đặc biệt nói là tuyên bố: "Khi xem xét đối số cho hàm tạo hoặc hàm chuyển đổi do người dùng xác định, chỉ cho phép một chuỗi chuyển đổi tiêu chuẩn (nếu không các chuyển đổi do người dùng xác định có thể được xâu chuỗi một cách hiệu quả)."
Jonathan Henson

Vâng, tôi vừa ném mã cùng nhau để kiểm tra một số gcctùy chọn trình biên dịch (mà nó không giống như có bất kỳ để giải quyết trường hợp cụ thể này). Tôi đã không nhìn sâu hơn vào nó (tôi phải làm việc :-) nhưng gccviệc tuân thủ tiêu chuẩn và việc sử dụng explicittừ khóa một tùy chọn trình biên dịch có thể được coi là không cần thiết.
Dave Rager 15/03/13

Chuyển đổi ngầm định không bị xiềng xích và Texturecó lẽ không nên được xây dựng ngầm (theo hướng dẫn trong các câu trả lời khác), vì vậy một trang web cuộc gọi tốt hơn sẽ được renderer.Draw(Texture("foo.png"));(giả sử nó hoạt động như tôi mong đợi).
Blaisorblade

3

Nó được gọi là chuyển đổi kiểu ngầm định. Nói chung đó là một điều tốt, vì nó ức chế sự lặp lại không cần thiết. Ví dụ: bạn tự động nhận std::stringphiên bản Drawmà không phải viết thêm bất kỳ mã nào cho nó. Nó cũng có thể hỗ trợ theo nguyên tắc đóng mở, vì nó cho phép bạn mở rộng Rendererkhả năng của mình mà không cần sửa đổi Rendererchính nó.

Mặt khác, nó không phải không có nhược điểm. Nó có thể gây khó khăn cho việc tìm ra một đối số đến từ đâu, vì một điều. Nó đôi khi có thể tạo ra kết quả bất ngờ trong các trường hợp khác. Đó là những gì các explicittừ khóa cho. Nếu bạn đặt nó trên hàm Texturetạo, nó sẽ vô hiệu hóa bằng cách sử dụng hàm tạo đó để chuyển đổi kiểu ngầm định. Tôi không biết về một phương pháp để cảnh báo toàn cầu về chuyển đổi loại ngầm, nhưng điều đó không có nghĩa là một phương thức không tồn tại, chỉ có điều gcc có số lượng tùy chọn lớn không thể hiểu được.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.