У меня есть функция как (не заботьтесь о возврате временного ссылкой. Это - просто пример для объяснения проблемы),
const foo<const int>& get_const()
{
foo<int> f;
return f;
}
Это, очевидно, не скомпилирует. Я ищу способ гарантировать, что вызывающие стороны не изменятся T
из foo
. Как я могу гарантировать это?
Я видел подобное поведение для boost::shared_ptr
. shared_ptr<T>
конвертируемо к const shared_ptr<const T>
. Я не мог выяснить, как это делает это.
Любая справка была бы большой.
Компилятор видит foo
и foo
как два совершенно разных и несвязанных типа, поэтому класс foo
должен поддерживать это точно так же, как и с любым другим преобразованием. Если у вас есть контроль над классом foo
, вам необходимо предоставить конструктор копирования или оператор неявного преобразования (или и то, и другое).
template<typename T>
class foo
{
public:
// Regular constructor
foo(T t) : t(t) {}
// Copy constructor (works for any type S convertable to T, in particular S = non-const T if T is const)
// Remember that foo<T> and foo<S> are unrelated, so the accessor method must be used here
template<typename S> foo (const foo<S>& copy) : t(copy.getT()) {}
// Accessor
T getT() const { return t; }
// Conversion operator
operator foo<const T> () const { return foo<const T>(t); }
private:
T t;
};
Предполагая, что Foo определен примерно так:
template<typename T> class Foo
{
public:
Foo(const T& value) : m_value(value) { }
const T& getValue() const { return m_value; }
void setValue(const T& value) { m_value = value; }
private:
T m_value;
};
Затем, чтобы гарантировать, что клиенты Foo не изменяют m_value (я предполагаю, что это что имеется в виду под фразой «Я ищу способ гарантировать, что вызывающие абоненты не изменят T of foo»), вам нужно константно квалифицировать объект Foo, а не его параметр шаблона, т.е.
Foo<int> x(1);
x.setValue(2); // OK
const Foo<int> y(1);
y.setValue(2); // does not compile
Следовательно, ваша функция get_foo должна вернуть const Foo
, а не const Foo
.
Вот полный, компилируемый пример:
#include <iostream>
template<typename T> class Foo
{
public:
Foo(const T& value) : m_value(value) { }
const T& getValue() const { return m_value; }
void setValue(const T& value) { m_value = value; }
private:
T m_value;
};
template<class T> class Owner
{
public:
Owner(const T& value) : m_foo(value) { }
Foo<T>& getFoo() { return m_foo; }
const Foo<T>& getConstFoo() const { return m_foo; }
private:
Foo<T> m_foo;
};
int main(int argc, char** argv)
{
Owner<int> x(1);
x.getFoo().setValue(2);
// x.getConstFoo().setValue(3); // will not compile
}
При сравнении языков важно помнить о том, что если вы делаете простой пошаговый перевод, вы не сравниваете яблоки с яблоками.
Что имеет смысл в одном языке может иметь ужасные побочные эффекты в другом. Чтобы действительно сравнить характеристики производительности, вам нужна версия C # и C++, и код для этих версий может быть очень разным. Например, в C # я бы даже не использовал одну и ту же сигнатуру функции. Я бы пошел с чем-то более похожим:
IEnumerable<int> Fibonacci()
{
int n1 = 0;
int n2 = 1;
yield return 1;
while (true)
{
int n = n1 + n2;
n1 = n2;
n2 = n;
yield return n;
}
}
и затем обернуть это так:
public static int fib(int n)
{
return Fibonacci().Skip(n).First();
}
Это будет гораздо лучше, потому что он работает снизу вверх, чтобы воспользоваться расчетами в последнем сроке, чтобы помочь построить следующий, а не два отдельных набора рекурсивных вызовов.
И если вы действительно хотите кричать производительность в C++ вы можете использовать мета-программирование, чтобы сделать компилятор предварительно вычислить ваши результаты следующим образом:
template<int N> struct fibonacci
{
static const int value = fibonacci<N - 1>::value + fibonacci<N - 2>::value;
};
template<> struct fibonacci<1>
{
static const int value = 1;
};
template<> struct fibonacci<0>
{
static const int value = 0;
};
-121--2776716- Также если вы просматриваете файловую систему с помощью обозревателя файлов netrw, вы можете установить текущий каталог, нажав клавишу c.
-121--746345-Прежде всего, вы возвращаете локальный объект по ссылке... это не хорошо.
foo и foo являются двумя различными типами, поэтому для их явного преобразования потребуется написать код (конструкторы преобразования).
Чтобы получить то, что вы хотели, рассмотрите следующее:
template <typename T>
struct foo {T* t;};
const foo<int>& get_const(const foo<int>& f) {
return f;
}
foo<int> f;
const foo<int>& cf = get_const(f);
f.t = 0; // ok, f is not const
*cf.t = 0; // ok because cf.t is const but what cf.t points to is not
cf.t = 0; // compiler error cf.t is const and cannot be lvalue
foo<int>& cf = get_const(f); // compiler error, cannot convert non-const to const without const_cast
Если вы правильно сделали инкапсуляцию и имеете доступ только к членам с установщиками const-getter и non-const, это должно быть достаточно хорошо для вас. Помните, если люди действительно хотят изменить ваш объект, они всегда могут const_cast. Конст-корректность - это только уловить непреднамеренные ошибки.
Если я не ошибаюсь, реализация boost :: shared_ptr
имеет неявный конструктор, который принимает константу Ссылка T &
в качестве аргумента, а затем использует const_cast
в указателе RHS для удаления const
, позволяя неявные преобразования между ними.
Примерно так:
shared_ptr(const shared_ptr<const T>& r) : ptr(const_cast<T*>(r.ptr)) {}
Это то, что вы ищете?