1. 程式人生 > >C++虛擬函式宣告和定義以及g++編譯遇到的一些問題

C++虛擬函式宣告和定義以及g++編譯遇到的一些問題

遇到了一些麻煩的,記錄下來作為教訓…..

1. 虛擬函式的宣告和定義

具體關於虛擬函式的知識不做多講,我在定義一個抽象類時,忘了將一個虛擬函式宣告為 純虛擬函式,又沒有對其定義, 導致編譯報錯時報錯如下:

undefined reference to `vtable for Fibonacci'

錯誤提示的很明顯,就是無法生成虛擬函式表。

我們知道,虛擬函式表(地址)在定義了虛擬函式的類所例項化的物件記憶體中的第一個位置,也就是在例項化過程中生成了虛表。這個錯誤提示在stackflow中最常見的解答就是類中聲明瞭虛擬函式,卻沒有定義。

總結一下虛擬函式宣告和定義的規則如下:

  • 類中的virtual函式,要麼設為純虛擬函式,要麼有定義,否則無法生成虛擬函式表。

    • 虛擬函式的可以類外定義,但是必須加上類名,類外定義不需要加virtual
    • 宣告為純虛擬函式,則類為抽象類,無法例項化,進一步強調,想要例項化有虛擬函式的類,必須對虛擬函式進行定義
  • 基類定義為虛擬函式,則子類同名函式也為虛擬函式,無論是否有virtual關鍵字修飾(一般宣告時加virtual,便於閱讀)

  • 凡是基類定義有虛擬函式,則基類需要定義虛解構函式(根據上一條法則,虛解構函式要麼有定義,要麼純虛,一般不設為純虛,可以定義空白)

  • 虛擬函式通過虛表實現,虛表是類例項化時生成在物件中的(虛表地址),所以如果一個類能夠例項化,則其虛擬函式必須有定義,如果不想定義虛擬函式,只能宣告為純虛擬函式,留給子類定義。

舉例如下:

基類為一個抽象類

// num_sequence.h
#ifndef _NUM_SEQUENCE

#define _NUM_SEQUENCE

#include <string>
#include <iostream>

using namespace std;

class num_sequence {

    public:
        num_sequence() {
            cout << " create a num sequence" << endl;
        }

        virtual
~num_sequence() { cout << "~num_sequence has been called" << endl; } virtual int elem(int pos) const= 0; virtual const string what_am_i() const = 0; static int max_elems(){ return _max_elems; } virtual ostream& print(ostream& = cout) const = 0; protected: virtual void gen_elems(int pos) const = 0; // const指標指向自認為const的一個物件, 這個物件不能通過const指標進行修改, 但可以通過其他方式進行修改 bool check_integrity(int pos) const{ if(pos <=0 || pos >_max_elems) { cerr << "Invalid position: " << pos << endl; return false; } return true; } const static int _max_elems = 1024; // 最大元素個數 }; #endif

子類不是抽象類,則必須實現基類的純虛擬函式

子類宣告:

// Fibonacci.h

#ifndef _FIBONACCI

#define _FIBONACCI

#include "num_sequence.h"
#include <vector>

using std::vector;

class Fibonacci : public num_sequence {

    public:
        Fibonacci(int len = 1, int beg_pos = 1): _length(len), _beg_pos(beg_pos){
            cout << "create a Fibonacci sequence" << endl;
        }

        virtual int elem(int pos) const ;    // 子類類中宣告virtual時, 應當和父類保持精確一致性

        virtual ostream& print(ostream &os=cout) const;

        virtual const string what_am_i() const{
            cout << " I am Fibonacci sequence" << endl;
        }


        int length() const {
            return _length;
        }

        int beg_pos() const{
            return _beg_pos;
        }

        ~Fibonacci() {
            cout << "~Fibonacci has been called" << endl;
        }

    protected:
        virtual void gen_elems(int pos) const;

        int                 _length;    // 長度
        int                 _beg_pos;   // 起始位置
        static vector<unsigned int>  _elems;     // 元素容器

};


#endif

子類虛擬函式定義:

// Fibonacci.cpp

#include "Fibonacci.h"

vector<unsigned int> Fibonacci::_elems;

// 類外定義virtual函式時, 不需要virtual關鍵字
int Fibonacci::elem(int pos) const {

    if(!check_integrity(pos))           // 檢查pos可用性
        return 0;

    if(pos > _elems.size() )            // 檢查當前元素個數是夠足夠, 當不足時自動生成
        Fibonacci::gen_elems(pos);      // 利用類作用域, 在編譯時指明呼叫函式  

    return _elems[pos - 1];
}

ostream& Fibonacci::print(ostream &os) const {

    int elem_pos = _beg_pos - 1;
    int end_pos = elem_pos + _length;

    if(end_pos > _elems.size()) {
        Fibonacci::gen_elems(end_pos);
    }

    while(elem_pos < end_pos) {
        os << _elems[elem_pos] << "\t";
        ++elem_pos;
    }
    return os;
}

void Fibonacci::gen_elems(int pos) const {

    if(_elems.empty()) {
        _elems.push_back(1);
        _elems.push_back(1);
    }

    if(_elems.size() <= pos) {
        int ix = _elems.size();
        int n_2 = _elems[ix-2];
        int n_1 = _elems[ix-1];

        for(; ix<=pos; ++ix){
            int elem = n_2 + n_1;
            _elems.push_back(elem);
            n_2 = n_1;
            n_1 = elem;
        }
    }
}

2. g++編譯多個cpp檔案

這個主要是由於我對g++用的不多,不熟悉規則。有兩個cpp檔案,Fibonacci.cpp(實現Fibonacci類), test.cpp(測試Fibonacci類)。

一開始沒注意,編譯命令如下:

g++ -O0 -std=c++11 -o run test_sequence.cpp Fibonacci.cpp

總是報錯提示找不到Fibonacci中的資料成員。

原因很簡單,編譯的順序不是隨意的,則Fibonacci.cpp必須在test_sequence.cpp前面

g++ -O0 -std=c++11 -o run Fibonacci.cpp test_sequence.cpp 

順利通過。