处理解除引用语法的C ++泛型类

时间:2017-01-12 14:18:58

标签: c++ templates generic-programming

处理某些类型需要使用的成员/方法访问这一事实的最佳方法是什么?运营商,而其他人使用 - >操作

最好为其编写代码。运算符并让调用者包装类型如下面的代码示例所示。

来自C#背景我不习惯这个特殊问题。

#include <iostream>
#include <string>
#include <vector>
#include <memory>

template<class T>
class container{
  public:
    void add(T element){
        elements_.push_back(std::move(element));   
    }

    void process(){
        for(auto& a: elements_){
            a.print();   
        }
    }
  private:
    std::vector<T> elements_;
};

class printable{
public:
    void print(){
        std::cout << "Print\n";   
    }
};

template<class T>
class printable_forwarder{
public:
    printable_forwarder(T element): element_{std::move(element)}{

    }

    void print(){
        element_->print();   
    }

private:
    T element_;
};

int main()
{
    container<printable> c1;
    c1.add(printable{});
    c1.process();

   container<printable_forwarder<std::shared_ptr<printable>>> c2;
   std::shared_ptr<printable> sp{std::make_shared<printable>()};
   c2.add(printable_forwarder<decltype(sp)>{sp});
   c2.process();
}

这看起来更好吗?

#include <iostream>
#include <string>
#include <memory>
#include <type_traits>
#include <vector>
template<typename T>
class dereference
{
public:
    inline static T& get(T& value){
        return value;
    }
};

template<typename T>
class dereference<T*>
{
public: 
    inline static typename std::add_lvalue_reference<typename std::remove_pointer<T>::type>::type get(T* value){
        return *value;
    }
};

template<typename T>
class dereference<std::shared_ptr<T>>
{
public: 
    inline static T& get(std::shared_ptr<T> value){
        return *value.get();
    }
};

template<class T>
class container{
public:
    void add(T const& v){
        items_.push_back(v);   
    }

    void print_all(){
        for(auto& a: items_){
            dereference<T>::get(a).print();   
        }
    }
private:
    std::vector<T> items_;
};

struct printable{
    void print(){
        std::cout << "Printing\n";   
    }
};

int main()
{
    container<printable> c1;
    c1.add(printable{});
    c1.print_all();

    container<std::shared_ptr<printable>> c2;
    c2.add( std::shared_ptr<printable>(new printable{}));
    c2.print_all();
}

6 个答案:

答案 0 :(得分:12)

  

处理某些类型需要使用的成员/方法访问这一事实的最佳方法是什么?运营商,而其他人使用 - &gt;操作

不要。

你的工作是写template<class T> class container。该容器包含T个。如果您的用户想要在T上执行某些操作,您应该展示执行某项操作的能力 - 但他们的责任可以正确执行该操作。否则,你只是添加了大量的代码膨胀。太棒了,你给了我一个打印所有元素的方法,但如果我知道在它们上面调用foo(),或者找到bar()返回大于42的东西的第一个元素怎么办?显然,您不会写for_each_foo()find_if_bar_is_42()

这就是标准库将容器与算法分开的原因。让容器尽可能可用的方法是让它通过iteratorbegin()展示两个end(),然后我可以做我需要做的任何事情作为用户:

container<T> values;
values.add(...);

// I know to use '.'
for (T& t : values) {
   t.print();
} 

container<T*> pointers;
pointers.add(...);

// I know to use '->'
for (T* t : pointers) {
    t->print();
}

auto iter = std::find_if(pointers.begin(), pointers.end(), [](T* t){
    return t->bar() == 42;
});

除此之外,你可以添加一堆本身带有callables的成员函数,所以你将工作传递给用户:

template <class F>
void for_each(F&& f) {
    for (auto& elem : elements_) {
        f(elem);              // option a
        std::invoke(f, elem); // option b, as of C++17
    }
}

所以上面的例子是:

values.for_each([](T& t){ t.print(); });
pointers.for_each([](T* t){ t->print(); });
values.for_each(std::mem_fn(&T::print));
pointers.for_each(std::mem_fn(&T::print));

请注意,始终由用户知道该怎么做。另外,如果您在std::invoke()的实施中使用for_each,那么您可以写一下:

pointers.for_each(&T::print);
values.for_each(&T::print);

,就此而言:

container<std::unique_ptr<T>> unique_ptrs;
unique_ptrs.for_each(&T::print);

答案 1 :(得分:2)

作为另一个答案中建议的打印机类型参数化container的替代方法,我建议改为container::process()方法的参数化:

template<typename F>
void process(F&& func)
{
    for (auto& e : elements)
    {
        func(e);
    }
}

然后客户端代码如下所示:

container<printable> value_container;
value_container.add(...);
value_container.process([](printable& obj) { obj.print(); });

container<printable*> ptr_container;
ptr_container.add(...);
ptr_container.process([](printable* obj) { obj->print(); });

答案 2 :(得分:2)

布莱尔,

我认为更惯用的现代方法是使用 traits 类型。此模式允许库作者创建一个协议,该协议将在常见情况下由库实现,但是可扩展,以便客户端可以支持任何必要的情况。

在下面的代码中,我将容器类放在名为 library 的命名空间中,并在名为<的命名空间中定义了 printable 类。 EM>客户端。我还要展示这种模式的客户端可扩展性,创建了一个名为 other_printable 的新客户端类型,它支持我们想要的功能(打印),但它有一个不同的API(有一个独立的地方) print ,而不是成员函数 print )。

traits类 print_traits 只是一个具有完全或部分特化的类型模板,其中一些由库提供,可能还有一些由客户端提供。在这种情况下,主模板有一个实现(它调用 print 成员函数)。在这种模式中,有时候没有主要的实现,每个案例都是专业化的。

图书馆想要支持的用例是:

  1. 具有打印成员函数的类型
  2. 支持类型的指针
  3. std :: unique_ptr支持的类型
  4. std :: shared_ptr支持的类型
  5. 因此,除了支持案例1的主模板之外,库作者还为其他三种情况(名称空间中的特化项)提供了特化。

    由于客户端想要使用不遵循库支持的API( print 成员)的类型,客户端只需创建 print_traits 专门化来处理不受支持的API(独立的打印功能)。

    请注意,通过添加此专门化,我们使 other_printable 成为受支持的类型,以便我们可以创建容纳指针(包括智能指针)的容器。

    还要注意特殊化模板定义,但与其专门化的主模板位于同一名称空间中。这意味着客户端代码必须打开命名空间以专门化 print_traits

    以下是代码:

    #include <iostream>
    #include <vector>
    #include <memory>
    
    // as if included from library.hpp
    namespace library
    {
    template <class T>
    struct print_traits
    {
        static void print(T const& t)
        {
            t.print();
        }
    };
    
    template <class T>
    struct print_traits<T*>
    {
        static void print(T* p)
        {
            print_traits<T>::print(*p);
        }
    };
    
    template <class T>
    struct print_traits<std::unique_ptr<T>>
    {
        static void print(std::unique_ptr<T>const& p)
        {
            print_traits<T>::print(*p);
        }
    };
    
    template <class T>
    struct print_traits<std::shared_ptr<T>>
    {
        static void print(std::shared_ptr<T>const& p)
        {
            print_traits<T>::print(*p);
        }
    };
    
    
    template<class T>
    struct container
    {
        void insert(T element)
        {
            elements_.push_back(std::move(element));   
        }
    
        void process()
        {
            for (auto const& a: elements_)
            {
                print_traits<T>::print(a);
            }
        }
      private:
        std::vector<T> elements_;
    };
    }
    
    // as if included from client.hpp (which would include library.hpp)
    namespace client
    {
        struct printable
        {
            void print() const
            {
                std::cout << "Print\n";
            }
        };
    
        struct other_printable {};
    
        void print(other_printable const&op)
        {
            std::cout << "Print\n";
        }
    
    }
    
    // template specializations must be in the same namespace as the primary
    namespace library
    {
        template <>
        struct print_traits<client::other_printable>
        {
            static void print(client::other_printable const& op)
            {
                client::print(op);
            }
        };
    }
    
    // main.cpp includes client.hpp
    int main()
    {
        using client::printable;
        using client::other_printable;
        using library::container;
    
        printable p0;
    
    
        container<printable> c0;
        c0.insert(p0);
        c0.process();
    
        container<printable*> c1;
        c1.insert(&p0);
        c1.process();
    
        container<std::unique_ptr<printable>> c2;
        c2.insert(std::make_unique<printable>());
        c2.process();
    
        container<std::shared_ptr<printable>> c3;
        c3.insert(std::make_shared<printable>());
        c3.process();
    
        other_printable op;
    
        container<other_printable> c4;
        c4.insert(op);
        c4.process();
    
        container<std::unique_ptr<other_printable>> c5;
        c5.insert(std::make_unique<other_printable>());
        c5.process();
    
    }
    

    我不得不指出,这种事情通常不会出现在C ++中,因为我们通常不想以同样的方式处理对象和指向它们的事物。也就是说,我希望这表明在特定情况下可以用来实现这一目标的方法。

答案 3 :(得分:1)

这只是在尝试自动化进程以检查容器存储的类型是否恰好是实际智能指针的指针类型时的警告。此警告来自增强文档:

  

重要的是is_pointer仅检测“真实”指针类型,而不是智能指针。用户不应该专门针对智能指针类型使用is_pointer,因为这样做可能会导致Boost(和其他第三方)代码无法正常运行。想要特征检测智能指针的用户应该创建自己的特征。但是,请注意,通常无法自动神奇地检测智能指针类型,因此这种特征必须部分专用于每个支持的智能指针类型。

可以找到here。我认为这与一般情况下的问题有关,因为它并没有完全回答这个问题,即在设计一个源代码的决策过程中提前知道它只是一个提示或好事。

答案 4 :(得分:0)

在这种情况下,我能想到的避免重复代码的最新解决方案是允许用户提供可选的功能对象来进行打印。例如:

template <typename T>
struct default_print {
  void operator()(T& t) {
    t.print();
  }
};

template <typename T, typename Printer = default_print<T>>
class container {
public:
  container() = default;

  container(Printer p) : printer(p) {
  }

  void add(T const& element) {
    elements.push_back(element);   
  }

  void process() {
    for (auto& e : elements) {
      printer(e);   
    }
  }

private:
  Printer printer;
  std::vector<T> elements;
};

这很像std::unique_ptr允许指定自定义删除器的方式。如果需要,可以使用empty base class optimization为无状态打印机获得零大小的开销。

你可以这样使用container

struct printable {
  void print() {}
};

template <typename T>
struct indirect_print {
  void operator()(T& t) {
    t->print();
  }
};

int main() {
  container<printable> c1;
  c1.process();

  container<printable*, indirect_print<printable*>> c2;
  c2.process();
}

如果您不喜欢打字,可以使用一些SFINAE来实现一个实用程序功能,如果T->运算符,则会自动使用其他打印机:

template <typename>
using void_t = void;

template <typename T, typename = void>
struct has_arrow_operator : std::false_type {
};

template <typename T>
struct has_arrow_operator<T, void_t<
    decltype(std::declval<T>().operator->())>> : std::true_type {
};

template <typename T>
struct has_arrow : std::integral_constant<bool,
    std::is_pointer<T>::value || has_arrow_operator<T>::value> {
};

template <typename T, typename = std::enable_if_t<!has_arrow<T>::value>>
container<T> make_container() {
  return container<T>();
}

template <typename T, typename = std::enable_if_t<has_arrow<T>::value>>
container<T, indirect_print<T>> make_container() {
  return container<T, indirect_print<T>>();
}

你可以这样使用make_container

int main() {
  auto c1 = make_container<printable>();
  c1.process();

  auto c2 = make_container<printable*>();
  c2.process();
}

您可以始终使用SFINAE直接在container类中执行打印机切换,但我觉得尽可能保持类的通用性,并将用例封装在实用程序函数中是一种更简洁的设计。 / p>

答案 5 :(得分:-1)

我不确定它是否是最佳解决方案,但您可以将这四种重载放在一个功能中:

template<typename T>
T& dereference(T& obj) {
    return obj;
}

template<typename T>
T& dereference(std::shared_ptr<T> obj) {
    return *obj;
}

template<typename T>
T& dereference(std::unique_ptr<T> obj) {
    return *obj;
}

template<typename T>
T& dereference(T* obj) {
    return *obj;
}

现在你可以向它传递任何对象,智能指针(弃用的std::auto_ptr除外)或原始指针:

int main() {
    int i = 3;
    auto ptr = std::make_shared<int>(5);

    std::cout << dereference(i) << ", " << dereference(ptr) << std::endl;

    return 0;
}

这将打印3, 5

但使用回调的恕我直言会更清晰。

template<class T>
class container {
private:
    std::vector<T> elements_;
    std::function<void(const T&)> callback_;

public:
    template<typename callback_t>
    container(callback_t callback) {
        callback_ = callback;
    }

    void add(T element){
        elements_.push_back(std::move(element));
    }

    void process() {
        for(auto& a: elements_){
            callback_(a);
        }
    }
};

现在,您可以在构造函数中传递回调:

container<int> c([](const int& val) {
    std::cout << val << std::endl;
});

c.add(3);
c.add(56);
c.add(4);

c.process();

请记住,您需要添加functional标头才能使用std::function