Cả hai (a)
và (b)
dẫn đến hành vi không xác định. Hành vi luôn luôn không xác định khi gọi một hàm thành viên thông qua một con trỏ null. Nếu hàm là tĩnh, về mặt kỹ thuật, nó cũng không được xác định, nhưng có một số tranh chấp.
Điều đầu tiên cần hiểu là tại sao hành vi không xác định lại bỏ qua một con trỏ null. Trong C ++ 03, thực sự có một chút mơ hồ ở đây.
Mặc dù "tham chiếu đến một con trỏ null dẫn đến hành vi không xác định" được đề cập trong các ghi chú trong cả §1.9 / 4 và §8.3.2 / 4, nó không bao giờ được nêu rõ ràng. (Ghi chú không mang tính quy chuẩn.)
Tuy nhiên, người ta có thể thử suy luận nó từ §3.10 / 2:
Một giá trị liên quan đến một đối tượng hoặc chức năng.
Khi tham chiếu, kết quả là một giá trị. Một con trỏ null không tham chiếu đến một đối tượng, do đó, khi chúng ta sử dụng lvalue, chúng ta có hành vi không xác định. Vấn đề là câu trước không bao giờ được nêu, vậy "sử dụng" lvalue nghĩa là gì? Thậm chí chỉ cần tạo ra nó, hay sử dụng nó theo nghĩa chính thức hơn để thực hiện chuyển đổi giá trị thành giá trị?
Bất kể, nó chắc chắn không thể được chuyển đổi thành rvalue (§4.1 / 1):
Nếu đối tượng mà lvalue tham chiếu đến không phải là đối tượng thuộc kiểu T và không phải là đối tượng thuộc kiểu dẫn xuất từ T hoặc nếu đối tượng chưa được khởi tạo, chương trình yêu cầu chuyển đổi này có hành vi không xác định.
Đây chắc chắn là hành vi không xác định.
Sự không rõ ràng xuất phát từ việc có hay không hành vi không xác định để xác định nhưng không sử dụng giá trị từ một con trỏ không hợp lệ (nghĩa là nhận một giá trị nhưng không chuyển đổi nó thành một giá trị). Nếu không, sau đó int *i = 0; *i; &(*i);
được xác định rõ. Đây là một vấn đề đang hoạt động .
Vì vậy, chúng tôi có một chế độ xem "bỏ tham chiếu một con trỏ null, nhận hành vi không xác định" nghiêm ngặt và một chế độ xem yếu "sử dụng một con trỏ null được tham chiếu, nhận hành vi không xác định".
Bây giờ chúng ta xem xét câu hỏi.
Có, (a)
dẫn đến hành vi không xác định. Trên thực tế, nếu this
là null thì bất kể nội dung của hàm là gì, kết quả là không xác định.
Điều này tuân theo §5.2.5 / 3:
Nếu E1
có kiểu "con trỏ tới lớp X", thì biểu thức E1->E2
được chuyển đổi thành dạng tương đương(*(E1)).E2;
*(E1)
sẽ dẫn đến hành vi không xác định với một diễn giải chặt chẽ và .E2
chuyển đổi nó thành một giá trị, làm cho hành vi không xác định đối với diễn giải yếu.
Nó cũng theo sau đó là hành vi không xác định trực tiếp từ (§9.3.1 / 1):
Nếu một hàm thành viên không tĩnh của lớp X được gọi cho một đối tượng không thuộc kiểu X hoặc thuộc kiểu dẫn xuất từ X, thì hành vi đó là không xác định.
Với các hàm tĩnh, việc giải thích chặt chẽ so với yếu tạo nên sự khác biệt. Nói một cách chính xác, nó không được xác định:
Một thành viên tĩnh có thể được đề cập đến bằng cách sử dụng cú pháp truy cập thành viên lớp, trong trường hợp này biểu thức đối tượng được đánh giá.
Có nghĩa là, nó được đánh giá giống như thể nó không tĩnh và một lần nữa chúng tôi bỏ qua một con trỏ null với (*(E1)).E2
.
Tuy nhiên, vì E1
không được sử dụng trong một lệnh gọi hàm thành viên tĩnh, nếu chúng ta sử dụng diễn giải yếu thì cuộc gọi được xác định rõ ràng. *(E1)
dẫn đến một giá trị, hàm tĩnh được giải quyết, *(E1)
bị loại bỏ và hàm được gọi. Không có chuyển đổi giá trị thành giá trị nào, vì vậy không có hành vi không xác định.
Trong C ++ 0x, kể từ n3126, sự mơ hồ vẫn còn. Bây giờ, hãy an toàn: sử dụng cách diễn giải chặt chẽ.