Tại sao tôi có thể sử dụng tự động trên một loại riêng tư?


139

Tôi đã ngạc nhiên khi mã sau đây biên dịch và chạy (vc2012 & gcc4.7.2)

class Foo {
    struct Bar { int i; };
public:
    Bar Baz() { return Bar(); }
};

int main() {
    Foo f;
    // Foo::Bar b = f.Baz();  // error
    auto b = f.Baz();         // ok
    std::cout << b.i;
}

Có đúng là mã này biên dịch tốt? Và tại sao nó đúng? Tại sao tôi có thể sử dụng autotrên một loại riêng tư, trong khi tôi không thể sử dụng tên của nó (như mong đợi)?


11
Quan sát đó f.Baz().icũng là OK, như là std::cout << typeid(f.Baz()).name(). Mã bên ngoài lớp có thể "nhìn thấy" loại được trả về bởi Baz()nếu bạn có thể giữ nó, bạn không thể đặt tên cho nó.
Steve Jessop

2
Và nếu bạn nghĩ nó thật kỳ lạ (điều mà bạn có thể làm, nhìn thấy khi bạn hỏi về nó) thì bạn không phải là người duy nhất;) Chiến lược này rất hữu ích cho những thứ như Thành ngữ Safe-Bool .
Matthieu M.

2
Tôi nghĩ điều cần nhớ là privatecó một sự thuận tiện để mô tả các API theo cách mà trình biên dịch có thể giúp thực thi. Người dùng không có ý định ngăn chặn truy cập vào loại Barcủa người dùng Foo, vì vậy nó không cản trở Foobất kỳ cách nào từ việc cung cấp quyền truy cập đó bằng cách trả lại một thể hiện của Bar.
Steve Jessop

1
"Có đúng là mã này biên dịch tốt không?" Không. Bạn cần phải #include <iostream>. ;-)
LF

Câu trả lời:


113

Các quy tắc cho auto, đối với hầu hết các phần, giống như đối với khấu trừ loại mẫu. Ví dụ được đăng hoạt động với cùng lý do bạn có thể chuyển các đối tượng thuộc loại riêng tư cho các hàm mẫu:

template <typename T>
void fun(T t) {}

int main() {
    Foo f;
    fun(f.Baz());         // ok
}

Và tại sao chúng ta có thể chuyển các đối tượng của các kiểu riêng tư cho các hàm mẫu, bạn yêu cầu? Bởi vì chỉ có tên của loại là không thể truy cập. Bản thân loại này vẫn có thể sử dụng được, đó là lý do tại sao bạn có thể trả nó về mã máy khách.


32
Và để thấy rằng sự riêng tư của tên không liên quan gì đến loại , hãy thêm public: typedef Bar return_type_from_Baz;vào lớp Footrong câu hỏi. Bây giờ loại có thể được xác định bằng một tên công khai, mặc dù được định nghĩa trong một phần riêng của lớp.
Steve Jessop

1
Để lặp lại quan điểm của @ Steve: công cụ xác định truy cập cho tên không liên quan gì đến loại của nó , như được thấy bằng cách thêm private: typedef Bar return_type_from_Baz;vào Foo, như đã trình bày . typedefSố nhận dạng không biết gì về truy cập, công khai và riêng tư.
damienh

Điều này không có nghĩa gì với tôi. Các tên của các loại chỉ đơn thuần là một bí danh cho các loại hình thực tế. Có vấn đề gì nếu tôi gọi nó Barhay SomeDeducedType? Nó không giống như tôi có thể sử dụng nó để đến với các thành viên tư nhân class Foohoặc bất cứ điều gì.
einpoklum

107

Kiểm soát truy cập được áp dụng cho tên . So sánh với ví dụ này từ tiêu chuẩn:

class A {
  class B { };
public:
  typedef B BB;
};

void f() {
  A::BB x; // OK, typedef name A::BB is public
  A::B y; // access error, A::B is private
}

12

Câu hỏi này đã được trả lời rất tốt bởi cả chill và R. Martinho Fernandes.

Tôi chỉ không thể bỏ qua cơ hội để trả lời một câu hỏi với sự tương tự Harry Potter:

class Wizard
{
private:
    class LordVoldemort
    {
        void avada_kedavra()
        {
            // scary stuff
        }
    };
public:
    using HeWhoMustNotBeNamed = LordVoldemort;

    friend class Harry;
};

class Harry : Wizard
{
public:
    Wizard::LordVoldemort;
};

int main()
{
    Wizard::HeWhoMustNotBeNamed tom; // OK
    // Wizard::LordVoldemort not_allowed; // Not OK
    Harry::LordVoldemort im_not_scared; // OK
    return 0;
}

https://ideone.com/I5q7gw

Cảm ơn Quentin đã nhắc nhở tôi về kẽ hở của Harry.


5
Không có friend class Harry;mất tích trong đó?
Quentin

@Quentin bạn hoàn toàn chính xác! Để hoàn thiện, có lẽ cũng nên thêm vào friend class Dumbledore;;)
jpihl

Harry không cho thấy rằng anh ta không sợ hãi bằng cách gọi Wizard::LordVoldemort;C ++ hiện đại. Thay vào đó, anh gọi using Wizard::LordVoldemort;. (Thành thật mà nói, thật không tự nhiên khi sử dụng Voldemort. ;-)
LF

8

Để thêm vào các câu trả lời (tốt) khác, đây là một ví dụ từ C ++ 98 minh họa rằng vấn đề thực sự không phải làm gì autocả

class Foo {
  struct Bar { int i; };
public:
  Bar Baz() { return Bar(); }
  void Qaz(Bar) {}
};

int main() {
  Foo f;
  f.Qaz(f.Baz()); // Ok
  // Foo::Bar x = f.Baz();
  // f.Qaz(x);
  // Error: error: ‘struct Foo::Bar’ is private
}

Sử dụng loại riêng không bị cấm, nó chỉ đặt tên loại. Chẳng hạn, việc tạo một loại tạm thời chưa được đặt tên của loại đó là ổn, trong tất cả các phiên bản của 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.