Type aliases and incomplete types

2019-06-28 04:38发布

问题:

I'm probably over reaching here to solve what should be a simple problem. I started this question here: Getting type of base class at compile time

Basically I'm trying to make the class manage it's own pointer types. I'm wrapping a C library where some structures have reference counting embedded in them, and others do not. Those that don't, I'd like to use shared_ptr. Those that do, I'd like to use intrusive_ptr. I'd like to avoid relying on programmer intellect to ensure the use of the proper wrapper. Eventually, I'd like to add more capability that relies on this behavior, but I'm not there yet.

@Yakk came up with an interesting solution using template type aliases, and I've tried to implement it. Unfortunately I've got myself in a spot where I can't seem to resolve circular references to the compiler's satisfaction. I get an "incomplete type 'Test2' named in nested name specifier" error pointing to the "using pointer=" line. I also get a bizarre "definition differs from declaration in return type" for my definition of Test::f(), but I suspect that might resolve itself once I get the first error resolved.

Most of the references I find on this error type involve sequencing of header files, but even with everything in one file I can't figure out how to order things to make this problem go away.

Any ideas?

#include <iostream>
#include <memory>


template<typename T>
class Pointered: public std::enable_shared_from_this<T>
{
public:
    using pointer=std::shared_ptr<T>;     //<-- incomplete type named error
    using weakPointer = std::weak_ptr<T>;
};


template<typename T>
using Ptr =  typename T:: pointer;

template<typename T>
using WkPtr = typename T:: weakPointer;


class Test2;

class Test:public Pointered<Test>
{
public:
    Ptr<Test2> f();
};


class Test2:public Pointered<Test2>
{
public:
    Ptr<Test> p;
    Test2(Ptr<Test> ptr):p(ptr){}
};


int main(int argc, const char * argv[])
{
    Ptr<Test> p=std::make_shared<Test>();
    Ptr<Test> p3=p;
    p->f();
    std::cout << "Refcount: " << p.use_count() << std::endl;
}


//definition differs from declaration in return type error here
Ptr<Test2> Test::f()
{
    return Ptr<Test2>(new Test2((Ptr<Test>)shared_from_this()));
}

回答1:

You cannot forward declare nested types, for that you need the definition of the enclosing type, but in your case you have a cyclic dependency that inhibits this.

The first thing to consider is whether the cyclic dependency is really a good idea. In most cases cyclic dependencies are considered a code smell (an indication of problems with the design). If you can remove the cyclic dependency, then everything will become easier.

A different alternative is moving the dependency on the nested type to a type-trait, that can be defined externally and before the definition of the types:

template <typename T>
struct pointer_traits;

template <>
struct pointer_traits<Test1> {
   typedef std::shared_ptr<Test1> ptr;
   typedef std::shared_ptr<Test1> wptr;
};

By moving the dependency outside of the real type, you no longer have the cyclic dependency (at the syntax level, you should still revisit the design). You can then add syntactic sugar as needed:

template <typename T>
using ptr = typename pointer_traits<T>::ptr;

class Test1 {
    ptr<Test2> p2;
};

If you really want the types to also be nested, you can use inheritance to bring those into scope, or in a simpler way just add the appropriate typedef:

class Test1 {
    typedef ptr<Test1> ptr_t;
// ...

Note, this is a rough approximation, if you opt for this approach you can work a bit on the details of the trait and the types to make it sweeter by adding more syntactic sugar, for example, you can provide two traits for shared_ptr_traits and intrusive_ptr_traits and provide a single line trait to determine from which of those you want to pull the typedefs, reducing the definition of the trait (per type) to a single line.