簡體   English   中英

如何在C ++中有效地實現異構不可變對象的不可變圖?

[英]How to efficiently implement an immutable graph of heterogenous immutable objects in C++?

出於好奇,我正在編寫一個編程語言文本解析器。 假設我想將標記的不可變(在運行時)圖形定義為頂點/節點。 這些自然是不同類型的 - 一些標記是關鍵字,一些是標識​​符等。但是它們都共享共同特征,其中圖形中的每個標記指向另一個標記。 此屬性允許解析器知道特定標記后面的內容 - 因此圖形定義了語言的正式語法。 我的問題是幾年前我每天都停止使用C ++,並且從那時起使用了很多更高級的語言,而且我的頭部在堆分配,堆棧分配等方面完全分散。 唉,我的C ++生銹了。

不過,我想立刻爬上陡峭的山坡,為自己設定以最高效的方式用這種命令式語言定義這個圖形的目標。 例如,我想避免使用'new'在堆上單獨分配每個令牌對象,因為我認為如果我將這些令牌的整個圖形背靠背地分配(以線性方式像數組中的元素一樣),根據參考原理的每個位置,這將有利於性能 - 我的意思是當整個圖形被壓縮以沿着內存中的“線”占據最小空間,而不是將所有其令牌對象放在隨機位置時,這是一個加號? 無論如何,就像你看到的,這是一個非常開放的問題。

class token
{

}

class word: token
{
    const char* chars;

    word(const char* s): chars(s)
    {
    }
}

class ident: token
{
    /// haven't thought about these details yet
}

template<int N> class composite_token: token
{
    token tokens[N];
}

class graph
{
    token* p_root_token;
}

當前的問題是:創建此圖形對象的過程是什么? 它是不可變的,它的思想結構在編譯時是已知的,這就是為什么我可以並且想要避免按值復制東西等等 - 應該可以用文字組成這個圖形嗎? 我希望我在這里有意義......(這不是我第一次沒有。)解析器在運行時將使用該圖作為編譯器的一部分。 僅僅因為這是C ++,我也會對C解決方案感到滿意。 非常感謝你提前。

我的C ++也生銹了,所以我可能不知道最好的解決方案。 但是,因為沒有其他人上前......

你是對的,在一個塊中分配所有節點會給你最好的位置。 但是,如果在程序啟動時動態分配圖形,則堆分配也可能會緊密聚集在一起。

要在單個內存塊中分配所有節點,我想到了兩種可能性:在啟動時創建並填充Vector <>(缺點是現在您在內存中有兩次圖形信息),或者使用靜態數組初始化程序“Node [] graph = {...};“

對於這兩種方法,最大的障礙是您想要創建異質對象的圖形。 一個顯而易見的解決方案是“不要”:您可以使您的節點成為所有可能字段的超集,並使用顯式“類型”成員區分類型。

如果要保留各種節點類,則必須使用多個數組/向量:每種類型一個。

無論哪種方式,節點之間的連接必須首先根據數組索引定義(Node [3]后跟Node [10])。 為了獲得更好的解析性能,您可以在程序啟動時根據這些索引創建直接對象指針。

我不會將文字字符串放入任何節點(在您的情況下為“word”):關鍵字,標識符和其他詞匯元素的識別應該在與解析器分開的詞法模塊中完成。 我認為如果你根據程序的輸入區分Lexer生成的標記和程序用來解析輸入的語法圖節點,那么它也會有所幫助。

我希望這有幫助。

我不知道你將如何定義一個定義任何實用編程語言語法的標記“圖形”,特別是如果標記之間的關系是“允許遵循”。

表示編程語言語法的常用方法是使用Backus-Naur Form(BNF)或稱為“EBNF”的擴展版本。

如果你想代表一個EBNF(“作為一個不可變圖”),這個SO答案討論了如何在C#中做到這一點。 這些想法在C ++中有直接的類比。

壞消息是大多數解析引擎都不能直接使用EBNF,因為它在實踐中效率太低。 使用語法規則的直接表示很難構建有效的解析器; 這就是人們發明解析器生成器的原因。 因此,除非您打算編寫解析器生成器,否則將這些規則放入內存結構中的需要,更不用說“高效”了。

最后,即使你以某種方式最佳地打包語法信息,它也可能不會在實際性能上產生一點差異。 解析器的大部分時間花在將字符分組為lexemes,有時甚至只是做空白抑制。

我不認為令牌的許多小分配將成為瓶頸,如果確實如此,你總是可以選擇一個內存池。

在問題上; 因為所有令牌都有類似的數據(指向下一個,並且可能是我們正在處理的令牌的枚舉值),你可以將類似的數據放在一個std :: vector中。 這將是內存中的連續數據,並且非常有效地循環。

循環時,您可以檢索所需的信息類型。 我敢打賭,令牌本身理想情況下只包含“動作”(成員函數),例如:如果前一個和下一個令牌是數字,而我是一個加號,我們應該將這些數字加在一起。

因此,數據存儲在一個中心位置,令牌被分配(但實際上可能不包含太多數據)並處理中心位置的數據。 這實際上是一種面向數據的設計。

矢量可能看起來像:

struct TokenData
{
    token *previous, *current, *next;
    token_id id; // some enum?
    ... // more data that is similar
}

std::vector<TokenData> token_data;

class token
{
    std::vector<TokenData> *token_data;
    size_t index;

    TokenData &data()
    {
        return (*token_data)[index];
    }

    const TokenData &data() const
    {
        return (*token_data)[index];
    }
}

// class plus_sign: token
// if (data().previous->data().id == NUMBER && data().next->data().id == NUMBER)

for (size_t i = 0; i < token_data.size(); i++)
{
    token_data[i].current->do_work();
}

這是個主意。

暫無
暫無

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

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