首页 > 解决方案 > Rvalue reference overloading && operator

问题描述

I have been trying examples from the book Design Patterns in Modern C++: Reusable Approaches for Object-Oriented Software Design by Dmitri Nesteruk and have been trying to compile an example from S.O.L.I.D. Design principles section.

Here is the code:

// This class demonstrates the Open-Closed 
// Principle (OCP) in the S.O.L.I.D. Desgin Principle.
// Software entities (classes, modules, functions, etc.)
// should be open for extension, but closed for modification

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

enum class Color { Red, Green, Blue };
enum class Size { Small, Medium, Large };

struct Product
{
  std::string name;
  Color color;
  Size size;

  explicit Product(std::string name, Color color, Size size) : name{name}, color{color}, size{size} { }  
};

// (Single-Responsibility Principle) our filtering process 
// into two part.
//    1) A filter (a process that takes all items and only returns some)
//    2) A specification (the definition of a predicate to apply to a data element)

// =============== Templates to allow classes to be extended =============== //

template <typename T> struct Specification
{
  virtual bool is_satisfied(T* item) = 0;
};

template <typename T> struct Filter {
  virtual std::vector<T*> filter(std::vector<T*>&, Specification<T>& ) = 0;
};

template< typename T > struct Display {
  virtual void display(const std::vector<T*>&) = 0;
};

// ========== Product Filter ========== //
struct BetterProductFilter : Filter<Product>{
  std::vector<Product*> filter(std::vector<Product*>& items, Specification<Product>& spec) override {
    std::vector<Product*> result;

    for (auto& p : items){
      if (spec.is_satisfied(p)){
        result.push_back(p);
      }
    }

    return result;
  }
};

// ========== Color specification ========== //
struct ColorSpecification : Specification<Product>{

  Color color;

  ColorSpecification(const Color color) : color{color} {}

  bool is_satisfied(Product* item) override { return item->color == color; }
};

// ========== Size specification ========== //

struct SizeSpecification : Specification<Product>{

  Size size;

  SizeSpecification(const Size size) : size{size} {}

  bool is_satisfied(Product* item) override { return item->size == size; }
  
};

// ========== Product Display ========== ///
struct ProductDisplay : Display<Product>{
  void display(const std::vector<Product*> &items){
    for(auto &item : items){
      std::cout << item->name << std::endl;
    }
  }

};

// =============== Allowing Composite Specifications =============== //
template <typename T> struct AndSpecification : Specification<T>
{
  Specification<T> &first;
  Specification<T> &second;

  AndSpecification(Specification<T> &first, Specification<T> &second) 
    : first(first), second(second) {}

  bool is_satisfied(T *item) override {
    return first.is_satisfied(item) && second.is_satisfied(item);
  }
};

// Overloading the && operator for two specifications
template <typename T> AndSpecification<T> operator&&
  (Specification<T>& first, Specification<T>& second){
  return AndSpecification<T>(first, second);
}

// ======= Rvalue Reference Doesn't work with our class implementation ====== //
template <typename T> AndSpecification<T> operator&&
  (Specification<T>&& first, Specification<T>&& second){
  return AndSpecification<T>(first, second);
}

int main(){

  // Initialization of Products
  Product apple{ "Apple", Color::Green, Size::Small };
  Product tree{ "Tree", Color::Green, Size::Large };
  Product house{ "House", Color::Blue, Size::Large };

  // Place all the products into a vector
  std::vector<Product*> all {&apple, &tree, &house};

  // Filter & Specification Objects
  BetterProductFilter filterObj;

  // Avoid making extra variables for specifications
  //ColorSpecification green(Color::Green);
  //SizeSpecification large(Size::Large);
  //auto green_and_large = green && large;

  auto green_and_large = ColorSpecification(Color::Green) && SizeSpecification(Size::Large);

  // Composite Specifications
  //AndSpecification<Product> green_and_large{ large, green };

  // Product Display Object 
  ProductDisplay disp;
  
  auto filtered_items = filterObj.filter(all, green_and_large);

  disp.display(filtered_items);

  return 0;
}

I overloaded the && operator to accepts two references to existing objects and that seems to work fine in this snippet of code:

// Overloading the && operator for two specifications
template <typename T> AndSpecification<T> operator&&
  (Specification<T>& first, Specification<T>& second){
  return AndSpecification<T>(first, second);
}

In main:

ColorSpecification green(Color::Green);
SizeSpecification large(Size::Large);
auto green_and_large = green && large;

But when I use Rvalue references I end up with a segmentation fault using this code:

// ======= Rvalue Reference Doesn't work with our class implementation ====== //
    template <typename T> AndSpecification<T> operator&&
      (Specification<T>&& first, Specification<T>&& second){
      return AndSpecification<T>(first, second);
    }

In main:

auto green_and_large = ColorSpecification(Color::Green) && SizeSpecification(Size::Large);

I'm guessing it is the fact this implementation calls AndSpecification(first, second) and this constructor only handles references to existing objects and not to rvalue references. How could I modify the code above to allow && operator to work with Rvalue references? Thanks!!!

标签: c++oopdesign-patternsoperator-overloadingconditional-operator

解决方案


你的结论是正确的。

如果你只是想获得一个程序。

// it is your ColorSpecification class with copy constructor addition
struct ProductColorSpecification final : Specification<Product> {
    Color color;
    ProductColorSpecification(const Color color)
    : Specification<Product>()
    , color{color}
    {}
    ProductColorSpecification(const ProductColorSpecification& o)
    : Specification<Product>()
    , color{o.color}
    {}
    bool is_satisfied(Product* item) const override { return item->color == color; }
};

// it is your SizeSpecification class with copy constructor addition
struct ProductSizeSpecification final : Specification<Product> {
    Size size;
    ProductSizeSpecification(const Size size)
    : Specification<Product>()
    , size{size} {}
    ProductSizeSpecification(const ProductSizeSpecification& o)
    : Specification<Product>()
    , size{o.size}
    {}
    bool is_satisfied(Product* item) const override { return item->size == size; }
};

struct ProductAndSpecification final : Specification<Product> {
    ProductColorSpecification *color_spec;
    ProductSizeSpecification  *size_spec ;
    ProductAndSpecification(const Specification<Product>& first_spec, const Specification<Product>& second_spec)
    : Specification<Product>()
    , color_spec{nullptr}
    , size_spec{nullptr}
    {
        const auto* spec_f_c = dynamic_cast<const ProductColorSpecification*>(&first_spec );
        const auto* spec_f_s = dynamic_cast<const ProductSizeSpecification *>(&first_spec );
        const auto* spec_s_c = dynamic_cast<const ProductColorSpecification*>(&second_spec);
        const auto* spec_s_s = dynamic_cast<const ProductSizeSpecification *>(&second_spec);
        if (spec_f_c && spec_s_s) {
            color_spec = new ProductColorSpecification(*spec_f_c);
            size_spec  = new ProductSizeSpecification (*spec_s_s);
        } else if (spec_f_s && spec_s_c) {
            color_spec = new ProductColorSpecification(*spec_s_c);
            size_spec  = new ProductSizeSpecification (*spec_f_s);
        } else {
            //  bad specs
        }
    }
    ~ProductAndSpecification()
    {
        if (color_spec)
            delete color_spec;
        if (size_spec)
            delete size_spec;
    }

    friend ProductAndSpecification operator&&(Specification<Product>&& first, Specification<Product>&& second);

    bool is_satisfied(Product *item) const override {
        if (color_spec && size_spec)
            return color_spec->is_satisfied(item) && size_spec->is_satisfied(item);
        else if (color_spec)
            return color_spec->is_satisfied(item);
        else if (size_spec)
            return size_spec->is_satisfied(item);
        else
            return false;
    }
};

struct ProductAndSpecification operator&&(Specification<Product>&& first, Specification<Product>&& second) {
    return ProductAndSpecification(first, second);
}

但它仍然是糟糕的设计(我想你明白为什么)。

PS:: 不要忘记在 main 函数中更改规范对象。


推荐阅读