分享
 
 
 

Implementing operator->* for Smart Pointers by scott meyers

王朝other·作者佚名  2006-01-10
窄屏简体版  字體: |||超大  

Implementing operator->* for Smart PointersDr. Dobb's Journal October 1999

Classes and member templates for smart pointersBy Scott MeyersScott is a C++ consultant and author of Effective C++ CD-ROM, Effective C++, and More Effective C++. You can contact him at http://www.aristeia.com/. Partial Template Specialization and operator->* When I wrote More Effective C++: 35 Ways to Improve Your Programs and Designs (Addison-Wesley, 1995), one of the topics I examined was smart pointers. As a result, I get a fair number of questions about them, and one of the most interesting questions came from Andrei Alexandrescu who asked, "Shouldn't a really smart smart pointer overload operator->*? I've never seen it done." I hadn't seen it done, either, so I set out to do it. The result is instructiveand for more than just operator->*. It also involves insights into interesting and useful applications of templates.

Review of operator->*If you're like most programmers, you don't use operator->* on a regular basis. Consequently, before I explain how to implement this operator for smart pointers, I'll review the behavior of the built-in version.

Given a class C, a pointer pmf to a parameterless member function of C, and a pointer pc to a C object, the expression

(pc->*pmf)(); // invoke the member function *pmf on *pc

invokes the member function pointed to by pmf on the object pointed to by pc. As you can see in Listing One, pointers to member functions behave similarly to pointers to regular functions; the syntax is just a little more complicated. By the way, the parentheses around pc->*pmf are necessary, because the compiler would interpret

pc->*pmf(); // error!

as

pc->*(pmf()); // error!

Designing Support for operator->*Like many operators, operator->* is binary: It takes two arguments. When implementing operator->* for smart pointers, the left argument is a smart pointer to an object of type T. The right argument is a pointer to a member function of class T. The only thing that can be done with the result of a call to operator->* is to hand it a parameter list for a function call, so the return type of operator- >* must be something to which operator() (the function call operator) may be applied. operator->*'s return value represents a pending member function call, so I'll call the type of object returned from operator->*, PMFC a "Pending Member Function Call."

Put all this together, and you get the pseudocode in Listing Two. Because each PMFC object represents a pending call to the member function passed to operator->*, both the member function and PMFC::operator() expect the same list of parameters. To simplify matters, I'll assume that T's member functions never take any arguments. (I'll remove this restriction below.) That means you can refine Listing Two as Listing Three.

But what is the return type of the member function pointed to by pmf? It could be int, double, or const Wombat&. It could be anything. You express this infinite set of possibilities in the usual fashionby using a template. Hence, operator->* becomes a member function template. Furthermore, PMFC becomes a template, too, because different instantiations of operator->* must return different types of PMFC objects. (That's because each PMFC object must know what type to return when its operator() is invoked.)

After templatization, you can abandon pseudocode and write PMFC and SP::operator->*; see Listing Four.

Zero-Parameter Member FunctionsPMFC represents a pending member function call, and needs to know two things to implement its operator(): the member function to call, and the object on which to invoke that member function. The PMFC constructor is the logical place to request these arguments. Furthermore, a standard pair object seems like a logical place to store them. That suggests the implementation in Listing Five.

Though it may not look it at first glance, it's all pretty simple. When creating a PMFC, you specify which member function to call and the object on which to invoke it. When you later invoke the PMFC's operator() function, it just invokes the saved member function on the saved object.

Note how operator() is implemented in terms of the built-in operator->*. Because PMFC objects are created only when a smart pointer's user-defined operator->* is called, that means that user-defined operator->*s are implemented in terms of the built-in operator->*. This provides nice symmetry with the behavior of the user-defined operator-> with respect to that of the built-in operator->, because every call to a user-defined operator-> in C++ ultimately ends in an (implicit) call to the built-in operator->. Such symmetry is reassuring. It suggests that the design is on the right track.

You may have noticed that the template parameters ObjectType, ReturnType, and MemFuncPtrType are somewhat redundant. Given MemFuncPtrType, it should be possible to figure out ObjectType and ReturnType. After all, both ObjectType and ReturnType are part of MemFuncPtrType. It is possible to deduce ObjectType and ReturnType from MemFuncPtrType using partial template specialization, but, because support for partial specialization is not yet common in commercial compilers, I've chosen not to use that approach here. For information on a design based on partial specialization, see the accompanying text box entitled "Partial Template Specialization and operator->*."

Given the implementation of PMFC in Listing Five, SP<T>'s operator->* almost writes itself. The PMFC object it returns demands an object pointer and a member function pointer. Smart pointers conventionally store an object pointer, and the necessary member function pointer is just the parameter passed to operator->* as in Listing Six. Consequently, the code in Listing Seven should work, and for the compilers with which I tested it (Visual C++ 6 and egcs 1.1.2), it does.

Yes, I know, the code has a resource leak (the newed Wombat is never deleted) and it employs a using directive (using namespace std;) when using declarations will do, but please try to focus on the interaction of SP::operator->* and PMFC instead of such relative minutiae. If you understand why the statements (pw-> *pmf)() behave the way they do, there's no doubt you can easily fix the stylistic shortcomings of this example.

By the way, because both the operator->* member functions and all the PMFC member functions are (implicitly) inline, you may hope that the generated code for the statement

(pw->*pmf)();

using SP and PMFC will be the same as the generated code for the equivalent

(pw.ptr->*pmf)();

which uses only built-in operations. The run-time cost of using SP's overloaded operator->* and PMFC's overloaded operator() could thus be zerozero additional bytes of code, zero additional bytes of data. The actual cost, of course, depends on the optimization capabilities of your compiler as well as on your standard library's implementation of pair and make_ pair. For the two compilers (and associated libraries) with which I tested the code (after enabling full optimization), one yielded a zero-run-time-cost implementation of operator->*, but the other did not.

Adding Support for const Member FunctionsLook closely at the formal parameter taken by SP<T>'s operator->* functions: It's ReturnType (T::*pmf)(). More specifically, it's not ReturnType (T::*pmf)() const. That means no pointer to a const member function can be passed to operator->*, and that means that operator->* fails to support const member functions. Such blatant const discrimination has no place in a well-designed software system. Fortunately, it's easy to eliminate. Simply add a second operator->* template to SP, one designed to work with pointers to const member functions; see Listing Eight. Interestingly, there's no need to change anything in PMFC. Its type parameter MemFuncPtrType, will bind to any type of member function pointer, regardless of whether the function in question is const.

Adding Support for Member Functions Taking ParametersWith the zero-parameter case under our belt, let's move on to support for pointers to member functions taking one parameter. The step is surprisingly small, because all you need to do is modify the type of the member-pointer parameter taken by operator->*, then propagate this change through PMFC. In fact, all you really need to do is add a new template parameter to operator->* (for the type of the parameter taken by the pointed-to member function), then update everything else to be consistent. Furthermore, because SP<T> should support member functions taking zero parameters as well as member functions taking one parameter, you add a new operator->* template to the existing one. In Listing Nine, I show only support for nonconst member functions, but operator->* templates for const member functions should be available, too.

Once you've got the hang of implementing support for zero and one parameters, it's easy to add support for as many as you need. To support member functions taking n parameters, declare two member template operator->*s inside SP, one to support nonconst member functions, one to support const ones. Each operator->* template should take n+1 type parameters, n for the parameters, and one for the return type. Add the corresponding operator() template to PMFC, and you're done. The source code for operator->*s taking up to two parameters (supporting both const and nonconst member functions) is available electronically; see "Resource Center," page 5.

Packaging Support for operator->*Many applications have several varieties of smart pointers and it would be unpleasant to have to repeat the foregoing work for each one (for an example of the different varieties of smart pointers that can be imagined, plus some killer-cool C++, see Kevin S. Van Horn's web site at http:// www.xmission.com/ ~ksvsoft/code/smart_ ptrs.html). Fortunately, support for operator->* can be packaged in the form of a base class, as in Listing Ten.

Smart pointers that wish to offer operator->* can then just inherit from SmartPtrBase. (This design applies only to smart pointers that contain dumb pointers to do the actual pointing. This is the most common smart pointer design, but there are alternatives. Such alternative designs may need to package operator->* functionality in a manner other than that described here.) However, it's probably best to use private inheritance, because the use of public inheritance would suggest the need to add a virtual destructor to SmartPtrBase, thus increasing its size (as well as the size of all derived classes). Private inheritance avoids this size penalty, though it mandates the use of a using declaration (see Listing Eleven) to make the privately inherited operator->* templates public. To package things even more nicely, both SmartPtrBase and the PMFC template could be put in a namespace.

Loose EndsAfter I'd developed this approach to implementing operator->* for smart pointers, I posted my solution to the Usenet newsgroup comp.lang.c++.moderated to see what I'd overlooked. It wasn't long before Esa Pulkkinen made these observations:

There are at least two problems with your approach:

1. You can't use pointers to data members (though this is easy enough to solve).

2. You can't use user-defined pointers-to-members. If someone has overloaded operator->* to take objects that act like member pointers, you may want to support such "smart pointers to members" in your smart pointer class. Unfortunately, you need traits classes to get the result type of such overloaded operator->*s.

Smart pointers to members! Yikes! Esa's right. (Actually, he's more right than I originally realized. Shortly after writing the draft of this article, one of my consulting clients showed me a problem that was naturally solved by smart pointers to members. I was surprised, too.) Fortunately, this article is long enough that I can stop here and leave ways of addressing Esa's observations in the time-honored form of exercises for the reader. So I will.

SummaryIf your goal is to make your smart pointers as behaviorally compatible with built-in pointers as possible, you should support operator->*, just like built-in pointers do. The use of class and member templates makes it easy to implement such support, and packaging the implementation in the form of a base class facilitates its reuse by other smart pointer authors.

AcknowledgmentsIn addition to motivating my interest in operator->* in the first place, Andrei Alexandrescu helped me simplify my implementation of PMFC. Andrei also provided insightful comments on earlier drafts of this paper and the accompanying source code, as did Esa Pulkkinen and Mark Rodgers. I am greatly indebted to each of them for their considerable help with this article.

DDJ

Listing Oneclass Wombat { // wombats are cute Australian marsupialspublic: // that look something like dogs int dig(); // return depth dug int sleep(); // return time slept};typedef int (Wombat::*PWMF)(); // PWMF--a pointer to a Wombat member functionWombat *pw = new Wombat; PWMF pmf = &Wombat::dig; // make pmf point to Wombat::dig(pw->*pmf)(); // same as pw->dig();pmf = &Wombat::sleep; // make pmf point to Wombat::sleep(pw->*pmf)(); // same as pw->sleep();Back to Article

class Wombat { // wombats are cute Australian marsupials

public: // that look something like dogs

int dig(); // return depth dug

int sleep(); // return time slept

};

typedef int (Wombat::*PWMF)(); // PWMF--a pointer to a Wombat member function

Wombat *pw = new Wombat;

PWMF pmf = &Wombat::dig; // make pmf point to Wombat::dig

(pw->*pmf)(); // same as pw->dig();

pmf = &Wombat::sleep; // make pmf point to Wombat::sleep

(pw->*pmf)(); // same as pw->sleep();

Back to Article

Listing Two class PMFC { // "Pending Member Function Call"public: ... return type operator()( parameters ) const; ...};template<typename T> // template for smart ptrs-to-Tclass SP { // supporting operator->*public: ... const PMFC operator->*( return type (T::*pmf)( parameters ) ) const; ...};Back to Article

class PMFC { // "Pending Member Function Call"

public:

...

return type operator()( parameters ) const;

...

};

template<typename T> // template for smart ptrs-to-T

class SP { // supporting operator->*

public:

...

const PMFC operator->*( return type (T::*pmf)( parameters ) ) const;

...

};

Back to Article

Listing Threeclass PMFC {public: ... return type operator()() const; ...};template<typename T>class SP { public: ... const PMFC operator->*( return type (T::*pmf)() ) const; ...};Back to Article

class PMFC {

public:

...

return type operator()() const;

...

};

template<typename T>

class SP {

public:

...

const PMFC operator->*( return type (T::*pmf)() ) const;

...

};

Back to Article

Listing Fourtemplate<typename ReturnType> // template for a pending mbr funcclass PMFC { // call returning type ReturnTypepublic: ... ReturnType operator()() const; ...};template<typename T>class SP { public: ... template<typename ReturnType> const PMFC<ReturnType> operator->*( ReturnType (T::*pmf)() ) const; ...};Back to Article

template<typename ReturnType> // template for a pending mbr func

class PMFC { // call returning type ReturnType

public:

...

ReturnType operator()() const;

...

};

template<typename T>

class SP {

public:

...

template<typename ReturnType>

const PMFC<ReturnType>

operator->*( ReturnType (T::*pmf)() ) const;

...

};

Back to Article

Listing Fivetemplate<typename ObjectType, // class offering the mem func typename ReturnType, // return type of the mem func typename MemFuncPtrType> // full signature of the mem funcclass PMFC { public: typedef std::pair<ObjectType*, MemFuncPtrType> CallInfo; PMFC(const CallInfo& info): callInfo(info) {} ReturnType operator()() const { return (callInfo.first->*callInfo.second)(); } private: CallInfo callInfo;};Back to Article

template<typename ObjectType, // class offering the mem func

typename ReturnType, // return type of the mem func

typename MemFuncPtrType> // full signature of the mem func

class PMFC {

public:

typedef std::pair<ObjectType*, MemFuncPtrType> CallInfo;

PMFC(const CallInfo& info): callInfo(info) {}

ReturnType operator()() const

{ return (callInfo.first->*callInfo.second)(); }

private:

CallInfo callInfo;

};

Back to Article

Listing Sixtemplate <typename T>class SP {public: SP(T *p): ptr(p) {} template <typename ReturnType> const PMFC<T, ReturnType, ReturnType (T::*)()> operator->*(ReturnType (T::*pmf)()) const { return std::make_pair(ptr, pmf); } ... private: T* ptr;};Back to Article

template <typename T>

class SP {

public:

SP(T *p): ptr(p) {}

template <typename ReturnType>

const PMFC<T, ReturnType, ReturnType (T::*)()>

operator->*(ReturnType (T::*pmf)()) const

{ return std::make_pair(ptr, pmf); }

...

private:

T* ptr;

};

Back to Article

Listing Seven#include <iostream>#include <utility>using namespace std; template<typename ObjectType, typename ReturnType, typename MemFuncPtrType>class PMFC { ... }; // as abovetemplate <typename T> // also as aboveclass SP { ... }; class Wombat { public: int dig() { cout << "Digging..." << endl; return 1; } int sleep() { cout << "Sleeping..." << endl; return 5; }};int main(){ // as before, PWMF is a typedef int (Wombat::*PWMF)(); // pointer to a Wombat member function SP<Wombat> pw = new Wombat; PWMF pmf = &Wombat::dig; // make pmf point to Wombat::dig (pw->*pmf)(); // invokes our operator->*; // prints "Digging..." pmf = &Wombat::sleep; // make pmf point to Wombat::sleep (pw->*pmf)(); // invokes our operator->*;} // prints "Sleeping..."Back to Article

#include <iostream>

#include <utility>

using namespace std;

template<typename ObjectType, typename ReturnType, typename MemFuncPtrType>

class PMFC { ... }; // as above

template <typename T> // also as above

class SP { ... };

class Wombat {

public:

int dig()

{

cout << "Digging..." << endl;

return 1;

}

int sleep()

{

cout << "Sleeping..." << endl;

return 5;

}

};

int main()

{ // as before, PWMF is a

typedef int (Wombat::*PWMF)(); // pointer to a Wombat member function

SP<Wombat> pw = new Wombat;

PWMF pmf = &Wombat::dig; // make pmf point to Wombat::dig

(pw->*pmf)(); // invokes our operator->*;

// prints "Digging..."

pmf = &Wombat::sleep; // make pmf point to Wombat::sleep

(pw->*pmf)(); // invokes our operator->*;

} // prints "Sleeping..."

Back to Article

Listing Eighttemplate <typename T>class SP {public: ... // as above template <typename ReturnType> const PMFC<T, ReturnType, ReturnType (T::*)() const> // const added operator->*(ReturnType (T::*pmf)() const) const // const added { return std::make_pair(ptr, pmf); } ... // as above};Back to Article

template <typename T>

class SP {

public:

... // as above

template <typename ReturnType>

const PMFC<T, ReturnType, ReturnType (T::*)() const> // const added

operator->*(ReturnType (T::*pmf)() const) const // const added

{ return std::make_pair(ptr, pmf); }

... // as above

};

Back to Article

Listing Ninetemplate <typename ObjectType, typename ReturnType, typename MemFuncPtrType>class PMFC {public: typedef pair<ObjectType*, MemFuncPtrType> CallInfo; PMFC(const CallInfo& info) : callInfo(info) {} // support for 0 parameters ReturnType operator()() const { return (callInfo.first->*callInfo.sd)(); } support for 1 parameter template <typename Param1Type> ReturnType operator()(Param1Type p1) const { return (callInfo.first->*callInfo.second)(p1); }private: CallInfo callInfo;}; template <typename T>class SP {public: SP(T *p): ptr(p) {} // support for 0 parameters template <typename ReturnType> const PMFC<T, ReturnType, ReturnType (T::*)()> operator->*(ReturnType (T::*pmf)()) const { return std::make_pair(ptr, pmf); } // support for 1 parameter template < typename ReturnType, typename Param1Type> const PMFC<T, ReturnType, ReturnType (T::*)(Param1Type)> operator->*(ReturnType (T::*pmf)(Param1Type)) const { return std::make_pair(ptr, pmf); } ... private: T* ptr;};Back to Article

template <typename ObjectType, typename ReturnType, typename MemFuncPtrType>

class PMFC {

public:

typedef pair<ObjectType*, MemFuncPtrType> CallInfo;

PMFC(const CallInfo& info)

: callInfo(info) {}

// support for 0 parameters

ReturnType operator()() const

{ return (callInfo.first->*callInfo.sd)(); }

support for 1 parameter

template <typename Param1Type>

ReturnType operator()(Param1Type p1) const

{ return (callInfo.first->*callInfo.second)(p1); }

private:

CallInfo callInfo;

};

template <typename T>

class SP {

public:

SP(T *p): ptr(p) {}

// support for 0 parameters

template <typename ReturnType>

const PMFC<T, ReturnType, ReturnType (T::*)()>

operator->*(ReturnType (T::*pmf)()) const

{ return std::make_pair(ptr, pmf); }

// support for 1 parameter

template < typename ReturnType, typename Param1Type>

const PMFC<T, ReturnType, ReturnType (T::*)(Param1Type)>

operator->*(ReturnType (T::*pmf)(Param1Type)) const

{ return std::make_pair(ptr, pmf); }

...

private:

T* ptr;

};

Back to Article

Listing Tentemplate <typename T> // base class for smart pointers wishingclass SmartPtrBase { // to support operator->*public: SmartPtrBase(T *initVal): ptr(initVal) {} // support for 0 parameters template <typename ReturnType> const PMFC<T, ReturnType, ReturnType (T::*)()> operator->*(ReturnType (T::*pmf)()) const { return std::make_pair(ptr, pmf); } // support for 1 parameter template < typename ReturnType, typename Param1Type> const PMFC<T, ReturnType, ReturnType (T::*)(Param1Type)> operator->*(ReturnType (T::*pmf)(Param1Type)) const { return make_pair(ptr, pmf); } ...protected: T* ptr;};Back to Article

template <typename T> // base class for smart pointers wishing

class SmartPtrBase { // to support operator->*

public:

SmartPtrBase(T *initVal): ptr(initVal) {}

// support for 0 parameters

template <typename ReturnType>

const PMFC<T, ReturnType, ReturnType (T::*)()>

operator->*(ReturnType (T::*pmf)()) const

{ return std::make_pair(ptr, pmf); }

// support for 1 parameter

template < typename ReturnType, typename Param1Type>

const PMFC<T, ReturnType, ReturnType (T::*)(Param1Type)>

operator->*(ReturnType (T::*pmf)(Param1Type)) const

{ return make_pair(ptr, pmf); }

...

protected:

T* ptr;

};

Back to Article

Listing Eleventemplate <typename T>class SP: private SmartPtrBase<T> {public: SP(T *p ): SmartPtrBase<T>(p) {} using SmartPtrBase<T>::operator->*; // make the privately inherited // operator->* templates public // normal smart pointer functions would go here; operator->* // functionality is inherited};Back to Article

template <typename T>

class SP: private SmartPtrBase<T> {

public:

SP(T *p ): SmartPtrBase<T>(p) {}

using SmartPtrBase<T>::operator->*; // make the privately inherited

// operator->* templates public

// normal smart pointer functions would go here; operator->*

// functionality is inherited

};

Back to Article

Listing Twelvetemplate <typename T> // traits classstruct MemFuncTraits {};template <typename R, typename O> // partial specializationstruct MemFuncTraits<R (O::*)()> { // for zero-parameter typedef R ReturnType; // non-const member typedef O ObjectType; // functions};template <typename R, typename O> // partial specializationstruct MemFuncTraits<R (O::*)() const> { // for zero-parameter typedef R ReturnType; // const member typedef O ObjectType; // functions};template <typename R, typename O, typename P1> // partial specializationstruct MemFuncTraits<R (O::*)(P1)> { // for one-parameter typedef R ReturnType; // non-const member typedef O ObjectType; // functions};template <typename R, typename O, typename P1> // partial specializationstruct MemFuncTraits<R (O::*)(P1) const> { // for one-parameter typedef R ReturnType; // const member typedef O ObjectType; // functions};Back to Article

template <typename T> // traits class

struct MemFuncTraits {};

template <typename R, typename O> // partial specialization

struct MemFuncTraits<R (O::*)()> { // for zero-parameter

typedef R ReturnType; // non-const member

typedef O ObjectType; // functions

};

template <typename R, typename O> // partial specialization

struct MemFuncTraits<R (O::*)() const> { // for zero-parameter

typedef R ReturnType; // const member

typedef O ObjectType; // functions

};

template <typename R, typename O, typename P1> // partial specialization

struct MemFuncTraits<R (O::*)(P1)> { // for one-parameter

typedef R ReturnType; // non-const member

typedef O ObjectType; // functions

};

template <typename R, typename O, typename P1> // partial specialization

struct MemFuncTraits<R (O::*)(P1) const> { // for one-parameter

typedef R ReturnType; // const member

typedef O ObjectType; // functions

};

Back to Article

Listing Thirteentemplate <typename MemFuncPtrType>class PMFC {public: typedef typename MemFuncTraits<MemFuncPtrType>::ObjectType ObjectType; typedef typename MemFuncTraits<MemFuncPtrType>::ReturnType ReturnType; ... // same as before};Back to Article

template <typename MemFuncPtrType>

class PMFC {

public:

typedef typename MemFuncTraits<MemFuncPtrType>::ObjectType ObjectType;

typedef typename MemFuncTraits<MemFuncPtrType>::ReturnType ReturnType;

... // same as before

};

Back to Article

Listing Fourteentemplate <typename MemFuncPtrType>const PMFC<MemFuncPtrType>operator->*(MemFuncPtrType pmf) const{ return std::make_pair(ptr, pmf); }template <typename MemFuncPtrType>

const PMFC<MemFuncPtrType>

operator->*(MemFuncPtrType pmf) const

{ return std::make_pair(ptr, pmf); }

 
 
 
免责声明:本文为网络用户发布,其观点仅代表作者个人观点,与本站无关,本站仅提供信息存储服务。文中陈述内容未经本站证实,其真实性、完整性、及时性本站不作任何保证或承诺,请读者仅作参考,并请自行核实相关内容。
2023年上半年GDP全球前十五强
 百态   2023-10-24
美众议院议长启动对拜登的弹劾调查
 百态   2023-09-13
上海、济南、武汉等多地出现不明坠落物
 探索   2023-09-06
印度或要将国名改为“巴拉特”
 百态   2023-09-06
男子为女友送行,买票不登机被捕
 百态   2023-08-20
手机地震预警功能怎么开?
 干货   2023-08-06
女子4年卖2套房花700多万做美容:不但没变美脸,面部还出现变形
 百态   2023-08-04
住户一楼被水淹 还冲来8头猪
 百态   2023-07-31
女子体内爬出大量瓜子状活虫
 百态   2023-07-25
地球连续35年收到神秘规律性信号,网友:不要回答!
 探索   2023-07-21
全球镓价格本周大涨27%
 探索   2023-07-09
钱都流向了那些不缺钱的人,苦都留给了能吃苦的人
 探索   2023-07-02
倩女手游刀客魅者强控制(强混乱强眩晕强睡眠)和对应控制抗性的关系
 百态   2020-08-20
美国5月9日最新疫情:美国确诊人数突破131万
 百态   2020-05-09
荷兰政府宣布将集体辞职
 干货   2020-04-30
倩女幽魂手游师徒任务情义春秋猜成语答案逍遥观:鹏程万里
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案神机营:射石饮羽
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案昆仑山:拔刀相助
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案天工阁:鬼斧神工
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案丝路古道:单枪匹马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:与虎谋皮
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:李代桃僵
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案镇郊荒野:指鹿为马
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:小鸟依人
 干货   2019-11-12
倩女幽魂手游师徒任务情义春秋猜成语答案金陵:千金买邻
 干货   2019-11-12
 
推荐阅读
 
 
 
>>返回首頁<<
 
靜靜地坐在廢墟上,四周的荒凉一望無際,忽然覺得,淒涼也很美
© 2005- 王朝網路 版權所有