Стиль кодирования при использовании стека для управления памятью в C / C ++ - PullRequest
2 голосов
/ 22 мая 2011

Исходя из фона Java, я пытаюсь научиться обрабатывать (де) выделение памяти в C / C ++ самым простым способом.

Коллега предложил мне выделять память только для переменных-членов ипусть стек обрабатывает локальные переменные.Я не совсем уверен, как называется эта концепция, но это означает, что функции будут реализованы так:

void inc(int x, int &y){
  y=x+1;
}

Другой способ будет следующим:

int inc(int x, int &y){
  y=x+1;
  return y;
}

Первый запрещаетя использую это в выражении, то есть:

int y;
inc(2,y); 
inc(y,y);

Второй делает, но это не красиво:

int y;
y=inc(inc(2,y),y);

Прежде чем я испорчу свой код, что делать опытнымПрограммисты C / C ++ думают об этом стиле кодирования?

Ответы [ 6 ]

5 голосов
/ 22 мая 2011

Я бы сильно отговорил

int inc(int x, int &y) {
   y=x+1;
   return y;
}

Программисту, использующему эту функцию, непонятно, почему функция изменяет ввод и возвращает значение , и они оба являются одним и тем же объектом .


Действительно, на мой взгляд, выбор между:

// #1
void inc(int x, int& y) {
   y=x+1;
}

int y = 0;
inc(2, y);

и

// #2
int inc(int x) {
   return x+1;
}

int y = inc(2);

В общем случае я все же предпочитаю#2 поскольку я нахожу "параметры" архаичными и неуклюжими в использовании.Как вы указали, вы в конечном итоге боретесь с выражениями, и не очень понятно, что происходит на самом деле, когда вы вызываете функцию 1 .

Опять же, если у вас есть объект более сложный, чемint (скажем, массив или большой класс, или вы просто хотите «вернуть» более одного объекта), с ним может быть легче иметь дело с владельцем объекта, если вы не создаете никаких новых объектов внутри функции,делая #1 более удобным выбором.

Я думаю, что вывод, который я здесь пытаюсь сделать, заключается в том, что зависит от сценария .Попытка обобщить эти вещи - дурацкое поручение.


1 - Использование указателей, а не ссылок, решает это несколько, хотя это и приводит к раздутию, поскольку теперь приходится беспокоиться о проверке на недействительностьуказатели:

// #3
void inc(int x, int* y) {
   assert(y); // at least, we can check that it's not NULL
   *y = x+1;
}

int y = 0;
inc(2, &y); // clear here that I'm passing a pointer
3 голосов
/ 22 мая 2011

Существует третий, гораздо более простой способ:

int inc( int x ) {
   return x+1;
}

int y = inc(inc(2));
1 голос
/ 22 мая 2011

Для типов примитивов это нормально:

int inc(int x) {
   return x+1;
}

для более сложных типов, чтобы избежать дополнительного копирования при возврате функции

void reverse_vector(const std::vector<int>& v, std::vector<int>* result) {
   if (!result) return;
   *result = v;
   std::reverse(result->begin(), result->end();
}
// ... 
std::vector<int> v;
std::vector<int> reversed;
reverse_vector(v, &reversed);

Для объекта, выделенного из кучи, я предлагаю использовать библиотеку boost :: shared_ptr (tr1 :: shared_ptr). Тогда вы можете кодировать почти так же, как в Java.

#include <string>
#include <boost/shared_ptr.hpp>
#include <boost/make_shared.hpp>

class A {
public:
    A(int x, const std::string& str) 
      : x(x), str(str) {
    }

    void foo() {
    }
private:
    int x;
    const std::string& str;
};

// ...

boost::shared_ptr<A> a = boost::make_shared<A>(1, "hello");
a->foo();

Вы можете рассматривать объекты boost :: shared_ptr как ссылки на java. Сборка мусора отсутствует (просто подсчет ссылок), поэтому вы должны позаботиться о циклах самостоятельно.

Имейте в виду, что shared_ptr немного медленнее стандартного указателя.

Также важно помнить, что вам следует избегать копирования больших объектов. Лучше написать

void foo(const std::string& str);

вместо

void foo(std::string str);

если вам не нужна копия str в foo.

Еще одна вещь - это то, что компилятор умен и сделает некоторые оптимизации для вас. Например, reverse_vector может быть записан как

std::vector<int> reverse_vector(std::vector<int> v) { // note copying!
   std::reverse(v.begin(), v.end());
   return v; // no additional copying of temporary due to RVO
}

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

1 голос
/ 22 мая 2011

Это не тот стиль программирования, о котором говорил ваш коллега. Типы POD, такие как целые числа или простые структуры, - это не те данные, которые вас обычно интересуют. Получение ресурсов - инициализация , или RAII, - это общая стратегия в C ++, которая использует свойство переменных, выделенных стеком, благодаря чему их деструктор гарантированно вызывается в большинстве ситуаций.

Код Faux-RAII:

// take a reference to some resource 'r'
void frob(resource& r, int val)
{
    other_resource or(val);

    or << r; // use of r requires no pointer manipulation, etc
} // 'or' is destructed at the end of 'frob'
  // even in exceptional situations.

int main (int argc, char argv[][])
{
    resource r(1, "a", 3.0);

    frob(r, 9);

    return 0; // after this 'r' will be destructed
}
0 голосов
/ 22 мая 2011

Я думаю, что во втором примере вам не нужна ссылка на y, поэтому вместо

int inc(int x, int &y)

вы должны иметь

int inc(int x, int y)

, потому что при редактированииy внутри вашей функции он снова редактирует оригинал y, а не просто локальную копию, а это не то, что вам нужно.

0 голосов
/ 22 мая 2011

Ваш вопрос не имеет никакого отношения к распределению памяти, только к передаче параметров по значению или по ссылке.

Эта функция передает параметр y в качестве ссылки, параметр x копируется.

void inc(int x, int &y){
  y=x+1;
}

Лично я считаю, что эта форма может стать источником серьезной головной боли, поскольку синтаксис не отклоняется от передачи по значению. Вам следует избегать этого, если только вы не имеете дело с объектами. Я предлагаю эту форму:

int inc(int x){
  return x+1;
}

y=inc(2)
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...