# typedef 与 using

# 对于typedefusing都能使用的场景

typedefC++98中引入的给类型创建别名的方法

usingC++11中引入的给类型创建别名的方法,using向下兼容typedef所支持的操作,同时引入了新功能,比typedef更强大,现代C++推荐优先使用using

  • 基本类型的别名
void foo()
{
    using INT = int; 
    INT a  = 12;
    cout << "Testing: " << a << endl;
    typedef double DOUBLE;
    DOUBLE b = 1.2;
    cout << "Testing: " << b << endl;

}

对于常规的类型,typedefusing的功能是相同的。

  • 对函数的别名
void fooFunc(int a, int b)
{
    cout << "Testing: " << a << " " << b << endl;
}

int funcAlias()
{
    typedef void (*FP)(int, int);
    FP fp = fooFunc;
    fp(1, 2);
    using FPU = void (*)(int, int);
    FPU fpu = fooFunc;
    fpu(3, 4);
}

如上代码中,声明了FPFPU类型的函数指针,虽然两者功能相同,但从代码看上使用using更加直观,一下子就能看出FPU是某类型的别名。

# usingtypedef强大的地方

  • 模板别名
template <typename T> 
class Array {
    private:
        T* ptr;
        int size;
    
    public:
        Array(T arr[], int s);
        ~Array();
        void print();
};

template <typename T> 
Array<T>::Array(T arr[], int s)
{
    ptr = new T[s];
    size = s;
    for (int i = 0; i < size; i++)
        ptr[i] = arr[i];
}

template <typename T> 
void Array<T>::print()
{
    for (int i = 0; i < size; i++)
        cout << " " << *(ptr + i);
    cout << endl;
}

template <typename T> 
Array<T>::~Array() 
{
    delete[] ptr;
}

template <typename T> 
using Vec = Array<T>;

int main(int argc, char **argv)
{
    int ar[5] = {10};
    Vec<int> v(ar, 5);
    v.print();
}

上面的代码中使用using定义了模板类Array的别名Vec,假如使用typedef来定义的话,代码在编译的时候就会报错如下,

/media/xx/data/code/basic_cplusplus_examples/test/src/utils.cpp:5:5: error: template declaration of ‘typedef’
    5 |     typedef Vec = Array<T>;
      |     ^~~~~~~
/media/xx/data/code/basic_cplusplus_examples/test/src/utils.cpp:5:13: error: ‘Vec’ does not name a type
    5 |     typedef Vec = Array<T>;

如果一定要用typedef实现,可以写成这样,

template <typename T>
struct Vec
{
  typedef Array<T> type;
};

// usage
Vec<int>::type vec;

不过,上面的代码就很丑了,而且还新定义了不必要的结构体,十分繁琐。

# autodecltype

  • auto

    • auto类型说明符,能让编译器替我们去分析表达式所属的类型,其通过初始值来推算变量的类型。因此auto定义的变量必须有初始值。
    • auto会忽略掉顶层的const,因此希望推断出的的auto类型是一个顶层const时,需要明确的指出。
    const int i = 100;
    auto j = i; // const auto j = i;
    j = 1000;
    cout << "j: " << j << endl;  
    

    上面的代码在编译运行的时候都不会报错,说明auto推理出的j的类型是不带const的。

  • decltype,这个关键字的引人是为了满足这种情况,当希望从表达式的类型推断出要定义的变量类型,但并不想用表达式的值初始化变量的时候,会使用decltype

    auto f = [](int a, int b){
        return a + b;
    };
    
    decltype(f(2, 3)) v = f(2, 3);    
    

    像这种使用方式,真觉得很魔幻。C++语法实在是冗杂。

# using在面向对象编程中的应用

看下面这种情况,先定义了基类,又定义了派生类。

class Base {
    public:
        Base(int i) {}
        Base(int a, int b) { }
        Base(int a, int b, int c) { }
};

class Derived : public Base {
    public:
        Derived(int i) : Base(i) {}
        Derived(int a, int b) : Base(a, b) {}
        Derived(int a, int b, int c) : Base(a, b, c) {}

        virtual void newMethod()
        {
            cout << "Testing: " << "New added Method.\n";
        }
};

因为使用派生类创建对象时,会先调用基类的构造函数再调用派生类的构造函数。
在对象销毁时先调用派生类的析构函数,再调用基类的析构函数。

如上面的代码中所展示,派生类中只是增加了一个newMethod方法,就需要对基类中的构造方法进行“补齐”,十分麻烦。

有没有什么简单的方法呢?using可以用来应对这种情况。

class Base {
    public:
        Base(int i) 
        {
            cout << "Calling Base Constructor.\n";
        }
        Base(int a, int b) { }
        Base(int a, int b, int c) { }

        virtual void printSelf()
        {
            cout << "Calling Base self print method.\n";
        }
};

class Derived : public Base {
    public:
        using Base::Base;
        using Base::printSelf;

        virtual void newMethod()
        {
            cout << "Calling Derived newMethod.\n";
        }
};

int main(int argc, char **argv)
{
    auto ptr = std::make_shared<Derived>(10);
    ptr->newMethod();
    ptr->printSelf();
}
// Calling Base Constructor.
// Testing: New added Method.
// Base: print self method.

从上面的例子可以看到,使用using声明后,就可以在派生类中使用基类的函数了。

值得注意的情况:

当使用多重继承时,使用using声明有可能导致歧义,在这种情况下就要小心使用using了。

    class BaseA {
        public:
            BaseA(int i) 
            {
                cout << "Calling BaseA Constructor.\n";
            }
            virtual void printSelf()
            {
                cout << "Print BaseA.\n";
            }
    };

    class BaseB {
        public:
            BaseB(int i) 
            {
                cout << "Calling BaseB Constructor.\n";
            }

            virtual void printSelf()
            {
                cout << "Print BaseB.\n";
            }
    };
    class Derived : public BaseA, public BaseB {
        public:
            using BaseA::BaseA;
            using BaseA::printSelf;

            using BaseB::BaseB;
            using BaseB::printSelf;

            virtual void newMethod()
            {
                cout << "Print newMethos.\n";
            }
    };

上面代码编译将会报错,

/usr/include/c++/9/ext/new_allocator.h:146:4: error: call of overloaded ‘Derived(int)’ is ambiguous

使用

using BaseA::BaseA;
using BaseA::printSelf;

using BaseB::BaseB;
using BaseB::printSelf;

将给程序带来两个歧义,一个是Derived类带一个整型变量的构造函数将不知道应该从BaseA还是BaseB中来继承;另外一个歧义来自printSelf,同样的原因。

要想消除歧义,必须显式的定义有歧义的函数,如下:

class BaseA {
    public:
        BaseA(int i)
        {
            cout << "Calling BaseA.\n";
        }

        virtual void printSelf()
        {
            cout << "Calling BaseA printSelf.\n";
        }
};

class BaseB {
    public:
        BaseB(int i)
        {
            cout << "Calling BaseB.\n";
        }

        virtual void printSelf()
        {
            cout << "Calling BaseB printSelf.\n";            
        }
};
class Derived : public BaseA, public BaseB {
    public:
        using BaseA::printSelf;
        using BaseB::printSelf;

        Derived() = default;
        Derived(int i)
        {
            cout << "Testing: " << "New added Method.\n";
        }
        
        void printSelf()
        {
            BaseA::printSelf();
            BaseB::printSelf();
        }
        virtual void newMethod();
};

如上,就可以整场编译运行了。

# reference