簡體   English   中英

用自己構造對象作為參考?

[英]Construct object with itself as reference?

我剛剛意識到這個程序編譯並運行(gcc版本4.4.5 / Ubuntu):

#include <iostream>
using namespace std;

class Test
{
public:
  // copyconstructor
  Test(const Test& other);
};

Test::Test(const Test& other)
{
  if (this == &other)
    cout << "copying myself" << endl;
  else
    cout << "copying something else" << endl;
}

int main(int argv, char** argc)
{
  Test a(a);              // compiles, runs and prints "copying myself"
  Test *b = new Test(*b); // compiles, runs and prints "copying something else"
}

我想知道為什么這個甚至可以編譯。 我假設(就像在Java中)參數在調用方法/構造函數之前被評估,所以我懷疑這個案例必須由語言規范中的某些“特殊情況”涵蓋?

問題:

  1. 有人可以解釋一下(最好是參考規范)嗎?
  2. 允許這個的理由是什么?
  3. 它是標准的C ++還是gcc特有的?

編輯1:我剛剛意識到我甚至可以寫int i = i;

編輯2:即使使用-Wall-pedantic ,編譯器也不會抱怨Test a(a);

編輯3:如果我添加一個方法

Test method(Test& t)
{
  cout << "in some" << endl;
  return t;
}

我甚至可以做Test a(method(a)); 沒有任何警告。

這個“被允許”的原因是因為規則說標識符范圍在標識符之后立即開始。 在這種情況下

int i = i;

RHS我在LHS之后“i”,所以我在范圍內。 這並不總是壞事:

void *p = (void*)&p; // p contains its own address

因為可以在不使用其值的情況下解決變量。 在OP的復制構造函數的情況下,不容易給出錯誤,因為綁定對變量的引用不需要初始化變量:它等同於獲取變量的地址。 合法的構造函數可以是:

struct List { List *next; List(List &n) { next = &n; } };

如果您看到參數僅被解決,則不使用其值。 在這種情況下,自引用實際上可能有意義:列表的尾部由自引用給出。 實際上,如果您將“next”的類型更改為引用,那么幾乎沒有選擇,因為您不能像指針一樣輕松地使用NULL。

像往常一樣,問題是倒退。 問題不在於為什么變量的初始化可以引用自身,問題是為什么它不能引用它。 [在菲利克斯,這是可能的]。 特別是,對於與變量相對的類型,轉發引用的能力的缺乏是非​​常破壞的,因為它阻止了除了使用不完整類型之外定義遞歸類型,這在C中已經足夠了,但是由於存在模板。

我不知道這與規范有什么關系,但我就是這樣看的:

當你做Test a(a); 它為堆棧上的a分配空間。 因此的位置a在存儲器中是已知的編譯器在開始main 當調用構造函數時(當然在此之前分配內存),正確的this指針會傳遞給它,因為它已知。

當你做Test *b = new Test(*b); ,你需要把它想象成兩個步驟。 首先分配和構造對象,然后將指向它的指針分配給b 你收到消息的原因是你實際上是在傳遞一個未初始化的指向構造函數的指針,並將它與對象的實際this指針進行比較(最終會將其指定給b ,但在構造函數退出之前不會)。

你使用new的第二個實際上更容易理解; 你在那里調用的內容完全相同:

Test *b;
b = new Test(*b);

而你實際上是在進行無效的解除引用。 嘗試在構造函數中添加一個<< &other <<到你的cout行,並制作它

Test *b = (Test *)0xFOOD1E44BADD1E5;

看到你正在通過堆棧上的指針傳遞任何值。 如果沒有明確初始化,那就是未定義的。 但是,即使你沒有用某種(in)理智的默認值初始化它,它也會與你發現的new的返回值不同。

首先,將其視為就地new Test a是一個局部變量而不是一個指針,它存在於堆棧中,因此它的內存位置總是很好定義 - 這與指針Test *b非常不同,除非顯式初始化為某個有效位置,否則它將懸空。

如果您編寫第一個實例,如:

Test a(*(&a));

你在那里調用的東西越來越清晰。

我不知道如何通過復制構造函數使編譯器不允許(或甚至警告)這種自我初始化。

第一個案例(可能)由3.8 / 6覆蓋:

在對象的生命周期開始之前,但在對象占用的存儲空間已經分配之后,或者在對象的生命周期結束之后,在對象占用的存儲空間被重用或釋放之前,任何引用原始對象的左值可以使用對象,但僅限於有限的方式。 這樣的左值是指分配的存儲(3.7.3.2),並且使用不依賴於其值的左值的屬性是明確定義的。

因為所有你正在使用的a (和other ,這勢必a )之前,其生命周期的開始是地址,我認為你是好:閱讀段落的其余部分的詳細規則。

請注意,盡管8.3.2 / 4表示“應該初始化引用以引用有效的對象或函數。” 有一些問題(如在標准的缺陷報告)什么是“有效”是指在這種情況下,這樣可能可以將參數不綁定other的未構造的(因此,“無效”?) a

所以,我不確定什么標准實際上是說在這里-我可以用一個左值,但不能將其綁定到一個參考,也許,在這種情況下, a 是不好的,而一個指針傳遞給a將是美好的,只要它僅以3.8 / 5允許的方式使用。

b的情況下,您在初始化之前使用該值(因為您取消引用它,並且因為即使您已經達到那么遠,並且&other將是b的值)。 這顯然不好。

與C ++一樣,它編譯是因為它不是違反語言約束,並且標准沒有明確要求診斷。 想象一下,當一個對象在其自己的初始化中被無效地使用時,為了強制執行診斷,規范必須經歷的扭曲,並想象編譯器可能必須做的數據流分析來識別復雜的情況(甚至可能不是如果指針是通過外部定義的函數走私的話,那么在編譯時是可能的。 更容易將其保留為未定義的行為,除非任何人對新規范語言有任何非常好的建議;-)

如果你提高了警告級別,你的編譯器可能會警告你使用未初始化的東西。 UB 不需要診斷,很多“明顯”錯誤的東西都可以編譯。

我不知道規范引用,但我知道訪問未初始化的指針總是會導致未定義的行為。

當我在Visual C ++中編譯代碼時,我得到:

test.cpp(20):警告C4700:使用未初始化的局部變量'b'

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM