C++開發:知識缺漏學習及ERROR集錦

版權聲明:本文爲博主原創文章,未經博主允許不得轉載。 https://blog.csdn.net/zehong1995/article/details/80911027

1.內存釋放問題

部分容器clear之後,其capacity還是不變的,並沒有釋放空間。這時候就需要用swap的trick或者C++11的shrink_to_fit來釋放空間。具體需要釋放空間的容器和注意的問題如下:
1)只有含 reserve()/capacity() 成員函數的容器才需要用swap的trick來釋放空間,而 C++ 裏只有 vector 和 string 這兩個符合條件。在 C++11 中可以直接使用 shrink_to_fit(),這個比swap好,簡單說就是更智能了,會考慮當前狀態下容器的空間和目標所需空間來決定是否釋放空間。
2)list/deque/set/map 等容器是沒有 reserve() 和 capacity() 這兩個成員函數的,因此 swap 是無用功(除非用戶代碼使用了定製的 per-object allocator)。

2.error: invalid use of non-static data member ’XXX’

翻譯過來的意思非法訪問了類裏的某些成員變量。最後發現是作用域的問題。
例如我在Test.h中聲明瞭函數:

std::unique_ptr<std::unordered_map<std::string,std::string>>& getMember();

在Test.cpp裏實現該函數:

std::unique_ptr<std::unordered_map<std::string,std::string>>& getMember() {
    return Test::ptr_of_Test;  //報錯 invalid use of non-static...
}

因爲,編譯器並不知道這個getMember是Test.h的,而且Test作用域裏的ptr_of_Test又不是靜態成員,所以報錯。所以實現Test.h的函數時要聲明函數作用域,正確如下:

std::unique_ptr<std::unordered_map<std::string,std::string>>& Test::getMember() { //加上Test::
    return Test::ptr_of_Test; //return ptr_of_Test;也可以,因爲當前作用域已經指定
}

3.error: passing ‘const xxx’ as ‘this’ argument of xxx discards qualifiers [-fpermissive]|

例如以下代碼:

#include <bits/stdc++.h>
using namespace std;
class Test {
public:
    Test () {}
    map<int,int>& getmap() {
        return Hash;
    }
    map<int,int>Hash;
};
void output(const Test& t){
    auto& curHash = t.getmap(); //報錯
    cout << curHash.size() << endl;
}
int main() {
    Test test;
    output(test);
}

出現該問題的原因是因爲我ouput的參數是一個const常量,儘管我t.getmap()得到的map我是隻讀,但編譯器不知道。因此解決辦法有3種:
1)將getmap函數申明爲常量

#include <bits/stdc++.h>
using namespace std;
class Test {
public:
    Test () {}
    map<int,int> getmap() const { //將函數申明爲常量
        return Hash;
    }
    map<int,int>Hash;
};
void output(const Test& t){
    map<int, int> curHash = t.getmap(); //但就實現不了變量引用
    cout << curHash.size() << endl;
}
int main() {
    Test test;
    output(test);
}

2)不要使用const參數
使用非const參數是最方便的,但是我們又怕代碼不小心改動了此參數,聲明爲const比較安全,且理論上效率更高。

#include <bits/stdc++.h>
using namespace std;
class Test {
public:
    Test () {}
    map<int,int>& getmap() {
        return Hash;
    }
    map<int,int>Hash;
};
void output(Test& t){  //使用非const參數
    auto& curHash = t.getmap();
    cout << curHash.size() << endl;
}
int main() {
    Test test;
    output(test);
}

3)放棄getmap函數

#include <bits/stdc++.h>
using namespace std;
class Test {
public:
    Test () {}
    map<int,int>& getmap() {
        return Hash;
    }
    map<int,int>Hash;
};
void output(const Test& t){
    auto& curHash = t.Hash;  //直接使用Hash,放棄使用getmap
    cout << curHash.size() << endl;
}
int main() {
    Test test;
    output(test);
}

以上是簡化代碼。其實我遇到的問題是:併發過程中更新模型,我用了一個buffMap數組,更新Index爲cur^1的Map,等到模型更新完畢再將Index更新爲cur^=1,這樣就避免的併發過程中的產生同時讀寫產生錯誤問題。基於此,這個模型更新過程需要引用不同index的map。所以我寫了一個getMap的函數,結果出現以上錯誤。
最終解決辦法是:我寫一個版本1)的getIndex,然後使用此Index來直接引用Public變量,也就是版本3)。

4.出現undefined的一個原因

以前打ACM都是隻有一個源文件,所有變量都來自當前文件和std的命名空間裏,對作用域的理解只停留在概念層級,現在才比較理解作用域意義。很多時候未定義的原因就是因爲沒有使用域運算符::。經常在編譯a.cpp的時候發現某些在a.h定義的變量顯示undefined,就是因爲沒有使用域運算符。作用域問題還可能出現2.中的error。

5.override和final的一點理解

5.1 override

由於virtual關鍵字並不是強制性的,因此有時候我們在子類中重寫的時候會錯寫參數類型、const關鍵字等,override則是顯示錶明該函數是重寫父類的函數,防止出錯。例如以下錯誤:
我們原意是想重寫父類print,但由於子類print的參數是short型,所以這是重載而不是重寫。
該程序輸出的是Base::print()-> 6而不是Child::print()-> 6

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    Base() {}
    virtual void print(short x) {
        cout << "Base::print()-> " << x <<endl;
    }
    map<int,int>mp;
    virtual ~Base() {}
};
class Child : public Base {
public:
    Child() {}
    void print(int x) {
        cout << "Child::print()-> " << x << endl;
    }
    ~Child() {}
};
int main()  {
    Base *base = new Child();
    base->print(6);
}

但如果在子類加上override關鍵字,void print(int x) override; 。則該程序會報錯 ‘void Child::print(int)’ marked ‘override’, but does not override。這就很好的解決了錯寫的問題。

下面再舉2個例子:

1)override報錯提示、只需要再聲明時加override關鍵字

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    Base() {}
    virtual void update();
    map<int,int>mp;
    virtual ~Base();
};
class Child : public Base {
public:
    Child() {}
    void update() override;
    ~Child() {}
};
void Child::update() {  // 2)
    mp[2] = 2;
}
int main() {
    Base *base = new Child();
    base->update();
    cout << base->mp.size() << endl;
    delete base;
}

a)error: ’void Child::update(int)’ marked ‘override’, but does not override
錯誤顯示此函數並不是在override。
例如以下兩個錯誤代碼:

class Child : public Base {
public:
    Child() {}
    void update() override {}        //正確
    void update1() override {}       //錯誤
    void update(int x) override {}   //錯誤
    ~Child() {}
};

b)error: virt-specifiers in ‘update’ not allowed outside a class definition
錯誤顯示override不允許在class外定義。
一開始在class裏定義了override方法,在class外部實現的時候還寫了override,多此一舉。
只要在class裏聲明override即可,實現並不需要加關鍵字。同理的還有virtual關鍵字。
錯誤代碼如下:

void Child::update() override{
    mp[2] = 2;
}

2)只要父類是虛函數,多重繼承後此函數還是虛函數

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    virtual void foo() {
        cout << "Base" <<endl;
    }
    void bar();
};

class A : public Base {
public:
    void foo() override {
        cout << 'A' << endl;
    }
};

class B : public A {
public:
    void foo() override {
        cout << 'B' << endl;
    };
};
int main()  {
    Base *base = new B();  //輸出B
    base->foo();
}

5.2 final

final關鍵字表示此類或此函數不能被繼承或重寫。我覺得通過以下這個例子的說明就能清晰理解final的作用。

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    virtual void foo() {
        cout << "Base" <<endl;
    }
    void bar();
};

class A : public Base {
public:
    void foo() override final { // 提示:A::foo 被覆寫且是最終覆寫
        cout << 'A' << endl;
    }
    void bar() final;           // 錯誤:非虛函數不能被override或final
    void bar() override;        // 錯誤:非虛函數不能被override或final
};

class B final : public A {      // 提示:類B聲明爲final
public:
    void foo() override {       // 錯誤:foo不能被覆寫,因爲它在A中是final
        cout << 'B' << endl;
    };
};
class C : public B {            // 錯誤:類B爲final類,不能再被繼承
    //..
};
int main()  {

}

7.虛函數和抽象類一點新理解

有虛函數,其子類同名同參同關鍵字函數則是重寫,是動態多態。
沒有虛函數,其子類同名函數則是重載,是靜態多態。
例如以下重載例子,輸出的是Base
若給Base的foo函數加上virtual關鍵字形成虛函數,則會輸出A
當然這是最基礎的虛函數的意義,不是我要說的新理解,順便寫上來而已。

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    void foo() {
        cout << "Base" <<endl;
    }
};

class A : public Base {
public:
    void foo() {
        cout << 'A' << endl;
    }
};
int main()  {
    Base* base = new A();
    base->foo();
}

另外的一個小積累,來自編譯過程中的一個鏈接錯誤:
error: undefined reference to `vtable for Base’.

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    Base() {
        cout << "BaseBuild." << endl;
        mp.clear();
        mp[1] = 1;
    }
    virtual void update(); //出錯的地方,因爲父類的update方法沒有實現
    map<int,int>mp;
    virtual ~Base();
};
class Child : public Base {
public:
    Child() {
        cout << "InheritBuild." << endl;
    }
    void update() override;
    ~Child() {
        cout << "InheritDestruct." << endl;
    }
};
void Child::update() {
    mp[2] = 2;
}
Base:: ~Base() {
    cout << "BaseDestruct." << endl;
}
int main() {
    Base *base = new Child();
    base->update();
    cout << base->mp.size() << endl;
    delete base;
}

因爲如果沒實現的話,update的虛表裏不知道內容。所以修復BUG的兩種做法:一種是實現父類的update方法,另外一種做法是如果將父類申明爲抽象類,也就是virtual void update() = 0; 抽象類不能實例化,只能由子類來實例化,即不能Base *base = new Base()。

8. 虛析構函數作用

簡單粗暴說:作用就是防止內存泄漏。
下面稍微詳細介紹下虛析構函數和普通析構函數的區別。
1)構造函數、析構函數、虛析構函數意義
構造函數:進行初始化成員變量的函數。
析構函數:在對象生命週期結束的時候,完成資源的回收和清理。如果我們在設計一個類的時候,沒有顯示聲明定義構造函數,析構函數,則編譯器會自動生成。
虛析構函數:只有當一個類被定義爲基類的時候,纔會把析構函數寫成虛析構函數。
2)爲啥使用虛析構函數?
我們創建一個基類的指針base,用完delete base時:
a. 如果基類的析構函數是虛函數,調用析構函數時會看base所賦值的對象,如果base賦值的對象是派生類的對象,就會調用派生類的析構函數,以此遞歸下去;如果base賦值的對象是基類的對象,就直接調用基類的析構函數即可,這樣不會造成內存泄露。
b. 如果基類的析構函數不是虛函數,調用析構函數時只會看指針的數據類型,而不會去看賦值的對象,這樣子類對象沒有析構,就會造成內存泄露。
3)一個看完就會清晰明瞭的例子:

#include <bits/stdc++.h>
using namespace std;
class Base {
public:
    Base() {
        cout << "Base construct." << endl;
    }
    ~Base() {
        cout << "Base destruct." << endl;
    }
};

class Child : public Base {
public:
    Child() {
        cout << "Child construct." << endl;
    }
    ~Child() {
        cout << "Child destruct." << endl;
    }
};
int main()  {
    Base* base = new Child();
    delete base;
    puts("##############");
    base = new Base();
    delete base;
}

運行結果如下:

Base construct.
Child construct.
Base destruct.
##############
Base construct.
Base destruct.

給Base類的析構函數加上virtual關鍵字(即:virtual ~Base();)後,結果如下:

Base construct.
Child construct.
Child destruct.
Base destruct.
##############
Base construct.
Base destruct.

9. [cpplint] non-const reference

cpplint是谷歌開源的代碼規範檢查。其中遇到一個比較有意思的檢查是:”error: Is this a non-const reference? If so, make const or use a pointer. “。
cpplint 不允許函數參數爲non-const 的reference。這並不是C++標準,google的解釋是減少引用和指針之間的混淆,還進一步規定函數參數要麼是pass-by-value 要麼是const reference,而返回值只允許是pointer。也就是說,const int& a, 要麼int* a,但不可以傳int& a。

10. future/promise簡介

11. error: expected unqualified-id before numeric constant

我出現這個問題的場景是:
我在(A.h)類裏定義了const static int BUFFERSIZE = 5,然而又在別的cpp文件(B.h)裏#define BUFFERSIZE 5,而且把在A.h中include了B.h,導致重定義,出現了這個問題

解決辦法就是:去掉兩者之一
測試是否是#define衝突,可以採用條件編譯來測試。

#ifdef XXX
    puts("Already define XXX");
#endif

12. error: invalid use of non-static data member

錯誤的簡化代碼:

#include <bits/stdc++.h>
using namespace std;
class Node {
    const int arraySize = 5;
    int num[arraySize];  //error
};
int main() {
    Node node;
}

出現這個問題的原因,比較不官方的解釋:因爲一個類可以有很多對象,每個對象都有一個arraySize,而不是“唯一的”,所以必須把arraySize申明爲靜態的,或者拿到類外邊。正確代碼如下:

#include <bits/stdc++.h>
using namespace std;
const int arraySize = 5;
class Node {
    int num[arraySize];
};
int main() {
    Node node;
}
#include <bits/stdc++.h>
using namespace std;
class Node {
    const static int arraySize = 5;
    int num[arraySize];
};
int main() {
    Node node;
}

13. 鎖對象lock_guard和unique_lock

14. typedef struct 和struct

以下代碼可以清晰瞭解兩者區別:

----------C語言定義結構體方式1----------
typedef struct Example {   //struct Example == Foo
    int a;
} Foo;
Foo foo;
struct Example foo;

----------C語言定義結構體方式2----------
typedef struct {
    int a;
} Foo;
Foo foo;

----------C++語言定義結構體方式----------
struct Foo {
    int a;
};
Foo foo;

15. using namespace

16. const_cast

17. 頭文件聲明順序

使用CPPLINT代碼檢查,遇到過以下錯誤:
Found C++ system header after other header. Should be: xxx.h, c system, c++ system, other
意思是:xxx.cpp的頭文件順序必須是:xxx.h、C語言頭文件、C++語言頭文件。
這就使我好奇頭文件聲明順序的作用:只是爲了規範?還是前後順序有影響程序?
答案是:會影響衝突內容,以先聲明的爲主。
測試代碼如下:

#include "math/MathFunctions.h" //順序1,裏面有自己實現的pow(double, int)函數
#include <cmath>                //順序2
#include <iostream>
int main()
{
    double base = 2.0;
    int exponent = 3;
    double result = std::pow(base, exponent);
    std::cout << result << std::endl;
    return 0;
}

報錯如下:
/usr/include/c++/5/cmath:418:26:
error: ‘double std::pow(double, int)’ conflicts with a previous declaration
解決辦法:
如果想使用自己MathFunctions裏定義的pow函數:把std::去掉
如果想使用std的pow函數:把cmath定義在”math/MathFunctions.h”之前
由此看出,CPPLINT規定順序是有好處的。通常情況下,先用自己定義的,然後兼容C語言的,再者C++的。

18. undefined reference to ‘dlsym’ ‘dlopen’ ‘dlclose’

undefined reference to dlsym'
undefined reference to
dlopen’
undefined reference to `dlclose’
解決辦法:
添加依賴庫 -ldl

19. cannot open shared object file: No such file or directory

  1. 確定是否含有此lib
  2. 執行ldconfig

20. emplace_back() 和push_back()

21. struct結構體的變量聲明加冒號

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章