Я могу использовать Любопытно Повторяющийся Шаблонный Шаблон здесь (C++)?

Я должен был передать права собственности на /plugins и /upgrade серверу, ничего больше.

$ cd /var/www/wordpress/wp-content
$ sudo chown www-data:www-data /plugings
$ sudo chown www-data:www-data /upgrade

Запуск сервера Apache в Ubuntu 18.04. Возможно, нужно будет изменить другие каталоги позже. В любом случае, я планирую восстановить разрешения, как только закончу редактирование, как предложено в этого ответа .

7
задан HostileFork 2 September 2015 в 13:42
поделиться

6 ответов

CRTP or compile-time polymorphism is for when you know all of your types at compile time. As long as you're using addWidget to collect a list of widgets at runtime and as long as fooAll and barAll then have to handle members of that homogenous list of widgets at runtime, you have to be able to handle different types at runtime. So for the problem you've presented, I think you're stuck using runtime polymorphism.

A standard answer, of course, is to verify that the performance of runtime polymorphism is a problem before you try to avoid it...

If you really need to avoid runtime polymorpism, then one of the following solutions may work.

Option 1: Use a compile-time collection of widgets

If your WidgetCollection's members are known at compile time, then you can very easily use templates.

template<typename F>
void WidgetCollection(F functor)
{
  functor(widgetA);
  functor(widgetB);
  functor(widgetC);
}

// Make Foo a functor that's specialized as needed, then...

void FooAll()
{
  WidgetCollection(Foo);
}

Option 2: Replace runtime polymorphism with free functions

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
  // (other virtual methods)
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> defaultFooableWidgets;
  vector<AbstractWidget*> customFooableWidgets1;
  vector<AbstractWidget*> customFooableWidgets2;      

 public:
  void addWidget(AbstractWidget* widget) {
    // decide which FooableWidgets list to push widget onto
  }

  void fooAll() {
    for (unsigned int i = 0; i < defaultFooableWidgets.size(); i++) {
      defaultFoo(defaultFooableWidgets[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets1.size(); i++) {
      customFoo1(customFooableWidgets1[i]);
    }
    for (unsigned int i = 0; i < customFooableWidgets2.size(); i++) {
      customFoo2(customFooableWidgets2[i]);
    }
  }
};

Ugly, and really not OO. Templates could help with this by reducing the need to list every special case; try something like the following (completely untested), but you're back to no inlining in this case.

class AbstractWidget {
 public:
  virtual AbstractWidget() {}
};

class WidgetCollection {
 private:
  map<void(AbstractWidget*), vector<AbstractWidget*> > fooWidgets;

 public:
  template<typename T>
  void addWidget(T* widget) {
    fooWidgets[TemplateSpecializationFunctionGivingWhichFooToUse<widget>()].push_back(widget);
  }

  void fooAll() {
    for (map<void(AbstractWidget*), vector<AbstractWidget*> >::const_iterator i = fooWidgets.begin(); i != fooWidgets.end(); i++) {
      for (unsigned int j = 0; j < i->second.size(); j++) {
        (*i->first)(i->second[j]);
      }
    }
  }
};

Option 3: Eliminate OO

OO is useful because it helps manage complexity and because it helps maintain stability in the face of change. For the circumstances you seem to be describing - thousands of widgets, whose behavior generally doesn't change, and whose member methods are very simple - you may not have much complexity or change to manage. If that's the case, then you may not need OO.

This solution is the same as runtime polymorphism, except that it requires that you maintain a static list of "virtual" methods and known subclasses (which is not OO) and it lets you replace virtual function calls with a jump table to inlined functions.

class AbstractWidget {
 public:
  enum WidgetType { CONCRETE_1, CONCRETE_2 };
  WidgetType type;
};

class WidgetCollection {
 private:
  vector<AbstractWidget*> mWidgets;

 public:
  void addWidget(AbstractWidget* widget) {
    widgets.push_back(widget);
  }

  void fooAll() {
    for (unsigned int i = 0; i < widgets.size(); i++) {
      switch(widgets[i]->type) {
        // insert handling (such as calls to inline free functions) here
      }
    }
  }
};
5
ответ дан 6 December 2019 в 07:52
поделиться

Краткий ответ: нет.

Длинный ответ (или все еще краткий, связанный с некоторыми другими ответами: -)

Вы динамически пытаетесь выяснить, какую функцию выполнить во время выполнения (вот что такое виртуальные функции). Если у вас есть вектор (чьи члены не могут быть определены во время компиляции), тогда вы не сможете понять, как встроить функции, что бы вы ни пытались.

Единственный недостаток в том, что если векторы всегда содержат одни и те же элементы (т.е. вы можете определить во время компиляции, что будет выполняться во время выполнения). Затем вы могли бы переделать это, но для хранения элементов потребуется нечто иное, кроме вектора (возможно, структура со всеми элементами в качестве членов).

Кроме того, вы действительно думаете, что виртуальная диспетчеризация является узким местом?
Лично я в этом очень сомневаюсь.

4
ответ дан 6 December 2019 в 07:52
поделиться

Simulated dynamic binding (there are other uses of CRTP) is for when the base class thinks of itself as being polymorphic, but clients only actually care about one particular derived class. So for instance you might have classes representing an interface into some platform-specific functionality, and any given platform will only ever need one implementation. The point of the pattern is to templatize the base class, so that even though there are multiple derived classes, the base class knows at compile time which one is in use.

It doesn't help you when you genuinely need runtime polymorphism, such as for example when you have a container of AbstractWidget*, each element can be one of several derived classes, and you have to iterate over them. In CRTP (or any template code), base and base are unrelated classes. Hence so are derived1 and derived2. There's no dynamic polymorphism between them unless they have another common base class, but then you're back where you started with virtual calls.

You might get some speedup by replacing your vector with several vectors: one for each of the derived classes that you know about, and one generic one for when you add new derived classes later and don't update the container. Then addWidget does some (slow) typeid checking or a virtual call to the widget, to add the widget to the correct container, and maybe has some overloads for when the caller knows the runtime class. Be careful not to accidentally add a subclass of WidgetIKnowAbout to the WidgetIKnowAbout* vector. fooAll and barAll can loop over each container in turn making (fast) calls to non-virtual fooImpl and barImpl functions that will then be inlined. They then loop over the hopefully much smaller AbstractWidget* vector, calling the virtual foo or bar functions.

It's a bit messy and not pure-OO, but if almost all your widgets belong to classes that your container knows about, then you might see a performance increase.

Note that if most widgets belong to classes that your container cannot possibly know about (because they're in different libraries, for example), then you can't possibly have inlining (unless your dynamic linker can inline. Mine can't). You could drop the virtual call overhead by messing about with member function pointers, but the gain would almost certainly be negligible or even negative. Most of the overhead of a virtual call is in the call itself, not the virtual lookup, and calls through function pointers will not be inlined.

Look at it another way: if the code is to be inlined, that means the actual machine code has to be different for the different types. This means you need either multiple loops, or a loop with a switch in it, because the machine code clearly can't change in ROM on each pass through the loop, according to the type of some pointer pulled out of a collection.

Well, I guess maybe the object could contain some asm code that the loop copies into RAM, marks executable, and jumps into. But that's not a C++ member function. And it can't be done portably. And it probably wouldn't even be fast, what with the copying and the icache invalidation. Which is why virtual calls exist...

7
ответ дан 6 December 2019 в 07:52
поделиться

Not if you need a vector of them. The STL containers are completely homogeneous, which means that if you need to store a widgetA and a widgetB in the same container, they must be inherited from a common parent. And, if widgetA::bar() does something different than widgetB::bar(), you have to make the functions virtual.

Do all of the widgets need to be in the same container? You could do something like

vector<widgetA> widget_a_collection;
vector<widgetB> widget_b_collection;

And then the functions wouldn't need to be virtual.

3
ответ дан 6 December 2019 в 07:52
поделиться

The problem that you will have here is with WidgetCollection::widgets. A vector can only contain items of one type, and using the CRTP requires that each AbstractWidget have a different type, templatized by the desired derived type. That is, you're AbstractWidget would look something like this:

template< class Derived >
class AbstractWidget {
    ...
    void foo() {
        static_cast< Derived* >( this )->foo_impl();
    }        
    ...
}

Which means that each AbstractWidget with a different Derived type would constitute a different type AbstractWidget< Derived >. Storing these all in a single vector won't work. So it looks like, in this case, virtual functions are the way to go.

3
ответ дан 6 December 2019 в 07:52
поделиться

Скорее всего, после всех этих усилий вы не увидите разницы в производительности.

Это абсолютно неправильный способ оптимизации. Вы бы не исправили логическую ошибку, изменяя случайные строки кода, не так ли? Нет, это глупо. Вы не «исправляете» код, пока сначала не обнаружите, какие строки на самом деле вызывают вашу проблему. Так почему же вы относитесь к ошибкам производительности по-другому?

Вам необходимо профилировать свое приложение и найти реальные узкие места. Затем ускорите этот код и повторно запустите профилировщик. Повторяйте, пока не исчезнет ошибка производительности (слишком медленное выполнение).

1
ответ дан 6 December 2019 в 07:52
поделиться
Другие вопросы по тегам:

Похожие вопросы: