C++右值引用

C++右值引用

右值引用應(yīng)該是C++11引入的一個非常重要的技術(shù)顷帖,因為它是移動語義(Move semantics)與完美轉(zhuǎn)發(fā)(Perfect forwarding)的基石:

  • 移動語義:將內(nèi)存的所有權(quán)從一個對象轉(zhuǎn)移到另外一個對象绊谭,高效的移動用來替換效率低下的復(fù)制虽界,對象的移動語義需要實現(xiàn)移動構(gòu)造函數(shù)(move constructor)和移動賦值運(yùn)算符(move asssignment operator)析显。
  • 完美轉(zhuǎn)發(fā):定義一個函數(shù)模板,該函數(shù)模板可以接收任意類型參數(shù),然后將參數(shù)轉(zhuǎn)發(fā)給其它目標(biāo)函數(shù)移斩,且保證目標(biāo)函數(shù)接受的參數(shù)其類型與傳遞給模板函數(shù)的類型相同。

左值與右值

在講解右值引用之前绢馍,你必須首先要區(qū)分兩個概念:左值與右值向瓷。但是精確講解清楚這兩個概念并不容易。首先舰涌,你要清楚左值與右值是C++中表達(dá)式的屬性猖任,在C++11中,每個表達(dá)式有兩個屬性:類型(type瓷耙,除去引用特性朱躺,用于類型檢查)和值類型(value category刁赖,用于語法檢查,比如一個表達(dá)式結(jié)果是否能被賦值)室琢。值類型包括3個基本類型:lvalue乾闰、prvaluexrvalue。后兩者又統(tǒng)稱為rvalue盈滴。lvalue我們稱為左值涯肩,你可以將左值看成是一個可以獲取地址的量,它可以用來標(biāo)識一個對象或函數(shù)巢钓。rvalue稱為右值病苗,你可以認(rèn)為所有不是左值的量就是右值,這是最簡單的解釋症汹。要準(zhǔn)確區(qū)分出右值中的prvaluexrvalue并不容易:大概前者就是純粹的右值硫朦,比如字面量,后者指的是可以被重用的臨時對象背镇。如果你感興趣咬展,你可以訪問cppreference去細(xì)究。但是瞒斩,你只要能夠區(qū)分開左值與右值就夠了破婆。

左值引用

C++11之前就已經(jīng)有了左值引用,有時候我們簡稱為引用胸囱,其語法很簡單:

int x = 20;
int& rx = x;   // 定義引用時必須初始化

但是引用也分為const引用與non-const引用祷舀,對于non-const引用,其只能用non-const左值來初始化:

int x = 20;
int& rx1 = x;   // non-const引用可以被non-const左值初始化
const int y = 10;
const int& rx2 = y;  // 非法:non-const引用不能被const左值初始化
int& rx3 = 10;      // 非法:non-const引用不能被右值初始化

但是const引用限制就少了:

int x = 10;
const int cx = 20;
const int& rx1 = x;   // const引用可以被non-const左值初始化
const int& rx2 = cx;  // const引用可以被const左值初始化
const int& rx3 = 9;   // const引用可以被右值初始化

理解上面并不難烹笔,因為你只要想著這樣初始化不會造成矛盾就好了裳扯,特別注意的是const左值引用可以接收右值(這點很重要,后面會說)谤职。

右值引用

C++11以前饰豺,右值被認(rèn)為是無用的資源,所以在C++11中引入了右值引用允蜈,就是為了重用右值冤吨。定義右值引用需要使用&&

int&& rrx = 200;

右值引用一定不能被左值所初始化,只能用左值初始化:

int x = 20;    // 左值
int&& rrx1 = x;   // 非法:右值引用無法被左值初始化
const int&& rrx2 = x;  // 非法:右值引用無法被左值初始化

那么為什么呢陷寝?因為右值引用的目的是為了延長用來初始化對象的生命周期,對于左值其馏,其生命周期與其作用域有關(guān)凤跑,你沒有必要去延長,這是我的理解叛复。既然是延長仔引,那么就出現(xiàn)了下面的情況:

int x = 20;   // 左值
int&& rx = x * 2;  // x*2的結(jié)果是一個右值扔仓,rx延長其生命周期
int y = rx + 2;   // 因此你可以重用它:42
rx = 100;         // 一旦你初始化一個右值引用變量,該變量就成為了一個左值咖耘,可以被賦值

這點很重要翘簇,初始化之后的右值引用將變成一個左值,如果是non-const還可以被賦值儿倒!

右值引用還可以用于函數(shù)參數(shù):

// 接收左值
void fun(int& lref)
{
    cout << "l-value reference\n";
}
// 接收右值
void fun(int&& rref)
{
    cout << "r-value reference\n";
}

int main()
{
    int x = 10;
    fun(x);   // output: l-value reference
    fun(10);  // output: r-value reference
}

可以看到版保,函數(shù)參數(shù)要區(qū)分開右值引用與左值引用,這是兩個不同的重載版本夫否。還有彻犁,如果你定義了下面的函數(shù):

void fun(const int& clref)
{
    cout << "l-value const reference\n";
}

但是其實它不僅可以接收左值,而且可以接收右值(如果你沒有提供接收右值引用的重載版本)凰慈。

移動語義

有了右值引用的概念汞幢,就可以理解移動語義了。前面說過微谓,一個對象的移動語義的實現(xiàn)是通過移動構(gòu)造函數(shù)與移動賦值運(yùn)算符來實現(xiàn)的森篷。所以,為了理解移動語義豺型,我們從一個對象出發(fā)仲智,下面創(chuàng)建一個動態(tài)數(shù)組類:

template <typename T>
class DynamicArray
{
public:
    explicit DynamicArray(int size) :
        m_size{ size }, m_array{ new T[size] }
    {
        cout << "Constructor: dynamic array is created!\n";
    }

    virtual ~DynamicArray()
    {
        delete[] m_array;
        cout << "Destructor: dynamic array is destroyed!\n";
    }

    // 復(fù)制構(gòu)造函數(shù)
    DynamicArray(const DynamicArray& rhs) :
        m_size{ rhs.m_size }
    {
        
        m_array = new T[m_size];
        for (int i = 0; i < m_size; ++i)
            m_array[i] = rhs.m_array[i];
        cout << "Copy constructor: dynamic array is created!\n";
    }

    // 復(fù)制賦值操作符
    DynamicArray& operator=(const DynamicArray& rhs)
    {
        cout << "Copy assignment operator is called\n";
        if (this == &rhs)
            return *this;

        delete[] m_array;
        
        m_size = rhs.m_size;
        m_array = new T[m_size];
        for (int i = 0; i < m_size; ++i)
            m_array[i] = rhs.m_array[i];

        return *this;
    }

    
    // 索引運(yùn)算符
    T& operator[](int index)
    {
        // 不進(jìn)行邊界檢查
        return m_array[index];
    }

    const T& operator[](int index) const
    {
        return m_array[index];
    }

    int size() const { return m_size; }
private:
    T* m_array;
    int m_size;
};

我們通過在堆上動態(tài)分配內(nèi)存來實現(xiàn)動態(tài)數(shù)組類,類中實現(xiàn)復(fù)制構(gòu)造函數(shù)触创、復(fù)制賦值操作符以及索引操作符坎藐。假如我們定義一個生產(chǎn)動態(tài)數(shù)組的工廠函數(shù):

// 生產(chǎn)int動態(tài)數(shù)組的工廠函數(shù)
DynamicArray<int> arrayFactor(int size)
{
    DynamicArray<int> arr{ size };
    return arr;
}

然后我們用下面的代碼進(jìn)行測試:

int main()
{
    {
        DynamicArray<int> arr = arrayFactor(10);
    }
    return 0;
}

其輸出為:

Constructor: dynamic array is created!
Copy constructor: dynamic array is created!
Destructor: dynamic array is destroyed!
Destructor: dynamic array is destroyed!

此時,我們來解讀這個輸出哼绑。首先岩馍,你調(diào)用arrayFactor函數(shù),內(nèi)部創(chuàng)建了一個動態(tài)數(shù)組抖韩,所以普通構(gòu)造函數(shù)被調(diào)用蛀恩。然后將這個動態(tài)數(shù)組返回,但是這個對象是函數(shù)內(nèi)部的茂浮,函數(shù)外是無法獲得的双谆,所以要生成一個臨時對象,然后用這個動態(tài)數(shù)組初始化席揽,函數(shù)最終返回的是臨時對象顽馋。我們知道這個動態(tài)數(shù)組即將消亡,所以其是右值幌羞,那么在構(gòu)建臨時對象時寸谜,會調(diào)用復(fù)制構(gòu)造函數(shù)(沒有右值的版本,但是右值可以傳遞給const左值引用參數(shù))属桦。但是問題又來了熊痴,因為你返回的這個臨時對象又拿去初始化另外一個對象arr他爸,當(dāng)然調(diào)用也是復(fù)制構(gòu)造函數(shù)。調(diào)用兩次復(fù)制構(gòu)造函數(shù)完全沒有必要果善,編譯器也會這么想诊笤,所以將其優(yōu)化:直接拿函數(shù)內(nèi)部創(chuàng)建的動態(tài)數(shù)組去初始化arr。所以僅有一次復(fù)制構(gòu)造函數(shù)被調(diào)用巾陕,但是一旦完成arr的創(chuàng)建讨跟,那個動態(tài)數(shù)組對象就被析構(gòu)了。最后arr離開其作用域被析構(gòu)惜论。我們看到編譯器盡管做了優(yōu)化许赃,但是還是導(dǎo)致對象被創(chuàng)建了兩次,函數(shù)內(nèi)部創(chuàng)建的動態(tài)數(shù)組僅僅是一個中間對象馆类,用完后就被析構(gòu)了混聊,有沒有可能直接將其申請的空間直接轉(zhuǎn)移到arr,那么資源得以重用乾巧,實際上只用申請一份內(nèi)存句喜。但是問題的關(guān)鍵是復(fù)制構(gòu)造函數(shù)執(zhí)行的是復(fù)制,不是轉(zhuǎn)移沟于,無法實現(xiàn)這樣的功能咳胃。此時第献,你需要移動構(gòu)造函數(shù):

template <typename T>
class DynamicArray
{
public:
        // ...其它省略
    
    // 移動構(gòu)造函數(shù)
    DynamicArray(DynamicArray&& rhs) :
        m_size{ rhs.m_size }, m_array{rhs.m_array}
    {
        rhs.m_size = 0;
        rhs.m_array = nullptr;
        cout << "Move constructor: dynamic array is moved!\n";
    }

    // 移動賦值操作符
    DynamicArray& operator=(DynamicArray&& rhs)
    {
        cout << "Move assignment operator is called\n";
        if (this == &rhs)
            return *this;
        delete[] m_array;
        m_size = rhs.m_size;
        m_array = rhs.m_array;
        rhs.m_size = 0;
        rhs.m_array = nullptr;

        return *this;
    }
};

上面是移動構(gòu)造函數(shù)與移動賦值操作符的實現(xiàn)询张,相比復(fù)制構(gòu)造函數(shù)與復(fù)制賦值操作符,前者沒有再分配內(nèi)存幌陕,而是實現(xiàn)內(nèi)存所有權(quán)轉(zhuǎn)移供璧。那么測試相同的代碼存崖,其結(jié)果是:

Constructor: dynamic array is created!
Move constructor: dynamic array is moved!
Destructor: dynamic array is destroyed!
Destructor: dynamic array is destroyed!

可以看到,調(diào)用的是移動構(gòu)造函數(shù)睡毒,那么函數(shù)內(nèi)部申請的動態(tài)數(shù)組直接被轉(zhuǎn)移到arr来惧。從而減少了一份相同內(nèi)存的申請與釋放。注意析構(gòu)函數(shù)被調(diào)用兩次演顾,這是因為盡管內(nèi)部進(jìn)行了內(nèi)存轉(zhuǎn)移供搀,但是臨時對象依然存在,只不過第一次析構(gòu)函數(shù)析構(gòu)的是一個nullptr钠至,這不會對程序有影響葛虐。其實通過這個例子,我們也可以看到棉钧,一旦你已經(jīng)自己創(chuàng)建了復(fù)制構(gòu)造函數(shù)與復(fù)制賦值運(yùn)算符后屿脐,編譯器不會創(chuàng)建默認(rèn)的移動構(gòu)造函數(shù)和移動賦值運(yùn)算符,這點要注意。最好的話摄悯,這個4個函數(shù)一旦自己實現(xiàn)一個,就應(yīng)該養(yǎng)成實現(xiàn)另外3個的習(xí)慣愧捕。

這就是移動語義奢驯,用移動而不是復(fù)制來避免無必要的資源浪費(fèi),從而提升程序的運(yùn)行效率次绘。其實在C++11中瘪阁,STL的容器都實現(xiàn)了移動構(gòu)造函數(shù)與移動賦值運(yùn)算符,這將大大優(yōu)化STL容器邮偎。

std::move

移動語義前面已經(jīng)介紹了管跺,我們知道對象的移動語義的實現(xiàn)是依靠移動構(gòu)造函數(shù)和移動賦值操作符。但是前提是你傳入的必須是右值禾进,但是有時候你需要將一個左值也進(jìn)行移動語義(因為你已經(jīng)知道這個左值后面不再使用)豁跑,那么就必須提供一個機(jī)制來將左值轉(zhuǎn)化為右值。在C++中泻云,std::move就是專為此而生艇拍,看下面的例子:

vector<int> v1{1, 2, 3, 4};
vector<int> v2 = v1;             // 此時調(diào)用復(fù)制構(gòu)造函數(shù),v2是v1的副本
vector<int> v3 = std::move(v1);  // 此時調(diào)用移動構(gòu)造函數(shù)宠纯,v3與v1交換:v1為空卸夕,v3為{1, 2, 3, 4}

可以看到,我們通過std::movev1轉(zhuǎn)化為右值婆瓜,從激發(fā)v3的移動構(gòu)造函數(shù)快集,實現(xiàn)移動語義。

C++中利用std::move實現(xiàn)移動語義的一個典型函數(shù)是std::swap:實現(xiàn)兩個對象的交換廉白。C++11之前个初,std::swap的實現(xiàn)如下:

template <typename T>
void swap(T& a, T& b)
{
    T tmp{a};  // 調(diào)用復(fù)制構(gòu)造函數(shù)
    a = b;     // 復(fù)制賦值運(yùn)算符
    b = tmp;     // 復(fù)制賦值運(yùn)算符
}

從上面的實現(xiàn)可以看到:共進(jìn)行了3次復(fù)制。如果類型T比較占內(nèi)存蒙秒,那么交換的代價是非常昂貴的勃黍。但是利用移動語義,我們可以更加高效地交換兩個對象:

template <typename T>
void swap(T& a, T& b)
{
    T temp{std::move(a)};   // 調(diào)用移動構(gòu)造函數(shù)
    a = std::move(b);       // 調(diào)用移動賦值運(yùn)算符
    b = std::move(tmp);     // 調(diào)用移動賦值運(yùn)算符
}

僅通過三次移動晕讲,實現(xiàn)兩個對象的交換覆获,由于沒有復(fù)制,效率更高瓢省!

你可能會想弄息,std::move函數(shù)內(nèi)部到底是怎么實現(xiàn)的。其實std::move函數(shù)并不“移動”勤婚,它僅僅進(jìn)行了類型轉(zhuǎn)換摹量。下面給出一個簡化版本的std::move:

template <typename T>
typename remove_reference<T>::type&& move(T&& param)
{
    using ReturnType = typename remove_reference<T>::type&&;
    
    return static_cast<ReturnType>(param);
}

代碼很短,但是估計很難懂。首先看一下函數(shù)的返回類型缨称,remove_reference在頭文件<type_traits>中凝果,remove_reference<T>有一個成員type,是T去除引用后的類型睦尽,所以remove_reference<T>::type&&一定是右值引用器净,對于返回類型為右值的函數(shù)其返回值是一個右值(準(zhǔn)確地說是xvalue)。所以当凡,知道了std::move函數(shù)的返回值是一個右值山害。然后,我們看一下函數(shù)的參數(shù)沿量,使用的是通用引用類型(&&)浪慌,意味者其可以接收左值,也可以接收右值朴则。其推導(dǎo)規(guī)則如下:如果實參是左值权纤,推導(dǎo)后的形參是左值引用,如果是右值乌妒,推導(dǎo)出來的是右值引用(感興趣的話可以看看reference collapsing)妖碉。但是不管怎么推導(dǎo),ReturnType的類型一定是右值引用芥被,最后std::move函數(shù)只是簡單地調(diào)用static_cast將參數(shù)轉(zhuǎn)化為右值引用欧宜。所以,std::move什么也沒有做拴魄,只是告訴編譯器將傳入的參數(shù)無條件地轉(zhuǎn)化為一個右值冗茸。所以,當(dāng)你使用std::move作用于一個對象時匹中,你只是告訴編譯器這個對象要轉(zhuǎn)化為右值夏漱,然后就有資格進(jìn)行移動語義了!

下面舉一個由于誤用std::move而無效的例子顶捷。假如你在設(shè)計一個標(biāo)注類挂绰,其構(gòu)造函數(shù)接收一個string類型參數(shù)作為標(biāo)注文本,你不希望它被修改服赎,所以標(biāo)注為const葵蒂,然后將其復(fù)制給其的一個數(shù)據(jù)成員,你可能會使用移動語義:

class Annotation
{
public:
    explicit Annotation(const string& text):
        m_text {std::move(text)}
    { }
    
    const string& getText() const { return m_text; }
private:
    string m_text;
};

然后你高高興興地去測試:

int main()
{
    string text{ "hello" };
    Annotation ant{ text };

    cout << ant.getText() << endl;  // output: hello
    cout << text << endl;           // output: hello 不是空重虑,移動語義沒有實現(xiàn)
    
    return 0;
}

你發(fā)現(xiàn)移動語義并沒有被實現(xiàn)践付,這是為什么呢?首先缺厉,從直觀上看永高,假如你移動語義成功了隧土,那么text會發(fā)生改變,這會違反其const屬性命爬。所以曹傀,你不大可能成功!其實饲宛,std::move函數(shù)會在推導(dǎo)形參時會保持形參的const屬性卖毁,所以其最終返回的是一個const右值引用類型,那么m_text{std::move(text)}到底會調(diào)用什么構(gòu)造函數(shù)呢落萎?我們知道string的內(nèi)部有兩個構(gòu)造函數(shù)可能會匹配:

class string
{
    // ...
    string(const string& rhs);   // 復(fù)制構(gòu)造函數(shù)
    string(string&& rhs);    // 移動構(gòu)造函數(shù)
}

那么到底會匹配哪個呢?肯定的是移動構(gòu)造函數(shù)不會被匹配炭剪,因為不接受const對象练链,復(fù)制構(gòu)造函數(shù)會匹配嗎?答案是可以奴拦,因為前面我們講過const左值引用可以接收右值媒鼓,const右值更可以!所以错妖,你其實調(diào)用了復(fù)制構(gòu)造函數(shù)绿鸣,那么移動語義當(dāng)然無法實現(xiàn)。

所以暂氯,如果你想接下來進(jìn)行移動潮模,那不要把std::move引用在const對象上!

std::forward與完美轉(zhuǎn)發(fā)

前面已經(jīng)講過痴施,完美轉(zhuǎn)發(fā)就是創(chuàng)建一個函數(shù)擎厢,該函數(shù)可以接收任意類型的參數(shù),然后將這些參數(shù)按原來的類型轉(zhuǎn)發(fā)給目標(biāo)函數(shù)辣吃,完美轉(zhuǎn)發(fā)的實現(xiàn)要依靠std::forward函數(shù)动遭。下面就定義了這樣一個函數(shù):

// 目標(biāo)函數(shù)
void foo(const string& str);   // 接收左值
void foo(string&& str);        // 接收右值

template <typename T>
void wrapper(T&& param)
{
    foo(std::forward<T>(param));  // 完美轉(zhuǎn)發(fā)
}

首先要有一點要明確,不論傳入wrapper的參數(shù)是左值還是右值神得,一旦傳入之后厘惦,param一定是左值,然后我們來具體分析這個函數(shù):

  • 當(dāng)一個類型為string類型的右值傳遞給wrapper時哩簿,T被推導(dǎo)為string宵蕉,param為右值引用類型,但是一旦傳入后节榜,param就變成了左值国裳,所以你直接轉(zhuǎn)發(fā)給foo函數(shù),將丟失param的右值屬性全跨,那么std::forward就確保傳入foo的值還是一個右值缝左;
  • 當(dāng)類型為const string的左值傳遞給wrapper時,T被推導(dǎo)為const string&param為const左值引用類型渺杉,傳入后蛇数,param仍為const左值類型,所以你直接轉(zhuǎn)發(fā)給foo函數(shù)是越,沒有問題耳舅,此時應(yīng)用std::forward函數(shù)可以看成什么也沒有做;
  • 當(dāng)類型為string的左值傳遞給wrapper時倚评,T被推導(dǎo)為string&浦徊,param為左值引用類型,傳入后天梧,param仍為左值類型盔性,所以你直接轉(zhuǎn)發(fā)給foo函數(shù),沒有問題呢岗,此時應(yīng)用std::forward函數(shù)可以看成什么也沒有做冕香;

所以wrapper函數(shù)可以實現(xiàn)完美轉(zhuǎn)發(fā),其關(guān)鍵點在于使用了std::forward函數(shù)確保傳入的右值依然轉(zhuǎn)發(fā)為右值后豫,而對左值傳入不做處理悉尾。

那么,std::forward到底怎么處理挫酿,其實現(xiàn)如下:

template<typename T> 
T&& forward(typename remove_reference<T>::type& param) 
{
    return static_cast<T&&>(param);
}

代碼依然與std::move一樣簡潔构眯,我們結(jié)合wrapper來看,如果傳入wrapper函數(shù)中的是string左值早龟,那么推導(dǎo)出Tstring&鸵赖,那么將調(diào)用std::foward<string&>,根據(jù)std::foward的實現(xiàn)拄衰,其實例化為:

string& && forward(typename remove_reference<string&>::type& param)
{
    return static_cast<string& &&>(param);
}

連續(xù)出現(xiàn)3個&符號有點奇怪它褪,我們知道C++不允許引用的引用,那么其實編譯器這里進(jìn)行是引用折疊(reference collapsing翘悉,大致就是后面的引用消掉)茫打,因此,變成:

string& forward(string& param)
{
    return static_cast<string&>(param);
}

上面的代碼就很清晰了妖混,一個左值引用的參數(shù)老赤,然后還是返回左值引用,此時的std::foward就是什么也沒有做制市,因為傳入與返回完全一樣抬旺。

那么如果傳入wrapper函數(shù)中的是string右值,那么推導(dǎo)出Tstring祥楣,那么將調(diào)用std::foward<string>开财,根據(jù)std::foward的實現(xiàn)汉柒,其實例化為:

string && forward(typename remove_reference<string>::type& param)
{
    return static_cast<string&&>(param);
}

繼續(xù)簡化,變成:

string&& forward(string& param)
{
    return static_cast<string&&>(param);
}

參數(shù)依然是左值引用(這點是一致的责鳍,因為前面說過傳入std:;forward中的實參一直是左值)碾褂,但是返回的是右值引用,此時的std::foward就是將一個左值轉(zhuǎn)化了右值历葛,這樣保證傳入目標(biāo)函數(shù)的實參是右值正塌!

綜上,可以看到std::foward函數(shù)是有條件地將傳入的參數(shù)轉(zhuǎn)化為右值恤溶,而std::move無條件地將參數(shù)轉(zhuǎn)化為右值乓诽,這是兩者的區(qū)別。但是本質(zhì)上咒程,兩者什么沒有做鸠天,最多就是進(jìn)行了一次類型轉(zhuǎn)換。

講完了孵坚!

References

[1] cpp leraning online.
[2] Marc Gregoire. Professional C++, Third Edition, 2016.
[3] cppreference
[4] 歐長坤(歐龍崎), 高速上手 C++ 11/14.
[5] Scott Meyers. Effective Modern C++, 2014.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市窥淆,隨后出現(xiàn)的幾起案子卖宠,更是在濱河造成了極大的恐慌,老刑警劉巖忧饭,帶你破解...
    沈念sama閱讀 219,539評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件扛伍,死亡現(xiàn)場離奇詭異,居然都是意外死亡词裤,警方通過查閱死者的電腦和手機(jī)刺洒,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,594評論 3 396
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來吼砂,“玉大人逆航,你說我怎么就攤上這事∮婕纾” “怎么了因俐?”我有些...
    開封第一講書人閱讀 165,871評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長周偎。 經(jīng)常有香客問我抹剩,道長,這世上最難降的妖魔是什么蓉坎? 我笑而不...
    開封第一講書人閱讀 58,963評論 1 295
  • 正文 為了忘掉前任澳眷,我火速辦了婚禮,結(jié)果婚禮上蛉艾,老公的妹妹穿的比我還像新娘钳踊。我一直安慰自己衷敌,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,984評論 6 393
  • 文/花漫 我一把揭開白布箍土。 她就那樣靜靜地躺著逢享,像睡著了一般。 火紅的嫁衣襯著肌膚如雪吴藻。 梳的紋絲不亂的頭發(fā)上瞒爬,一...
    開封第一講書人閱讀 51,763評論 1 307
  • 那天,我揣著相機(jī)與錄音沟堡,去河邊找鬼侧但。 笑死,一個胖子當(dāng)著我的面吹牛航罗,可吹牛的內(nèi)容都是我干的禀横。 我是一名探鬼主播,決...
    沈念sama閱讀 40,468評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼粥血,長吁一口氣:“原來是場噩夢啊……” “哼柏锄!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起复亏,我...
    開封第一講書人閱讀 39,357評論 0 276
  • 序言:老撾萬榮一對情侶失蹤趾娃,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后缔御,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體抬闷,經(jīng)...
    沈念sama閱讀 45,850評論 1 317
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 38,002評論 3 338
  • 正文 我和宋清朗相戀三年耕突,在試婚紗的時候發(fā)現(xiàn)自己被綠了笤成。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,144評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡眷茁,死狀恐怖炕泳,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情上祈,我是刑警寧澤喊崖,帶...
    沈念sama閱讀 35,823評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站雇逞,受9級特大地震影響荤懂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜塘砸,卻給世界環(huán)境...
    茶點故事閱讀 41,483評論 3 331
  • 文/蒙蒙 一节仿、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧掉蔬,春花似錦廊宪、人聲如沸矾瘾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,026評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽壕翩。三九已至,卻和暖如春傅寡,著一層夾襖步出監(jiān)牢的瞬間放妈,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,150評論 1 272
  • 我被黑心中介騙來泰國打工荐操, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留芜抒,地道東北人。 一個月前我還...
    沈念sama閱讀 48,415評論 3 373
  • 正文 我出身青樓托启,卻偏偏與公主長得像宅倒,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子屯耸,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,092評論 2 355

推薦閱讀更多精彩內(nèi)容