Metody (template) dla array helper'a

0

Panowie,

Zaczynam sobie przygotowywac custom helper'a dla wielowymiarowych tablic. Zaczalem od dwoch metod template:

  1. inicjalizacja tablicy:
		template <typename T>
		T** initArr2DT(T** arr2DPtr, int iLength, int jLength)
		{
			arr2DPtr = new T*[iLength];

			for (int i = 0; i < iLength; i++)
			{
				arr2DPtr[i] = new T[jLength];

				memset(arr2DPtr[i], '\0', sizeof(arr2DPtr[i]));
			}

			return arr2DPtr;
		}
  1. kopiowanie tablicy:
		template <typename T>
		T** copyArr2DT(T** arr2DPtr, T** arr2DCopyPtr, int iLength, int jLength)
		{
			arr2DCopyPtr = new T*[iLength];

			for (int i = 0; i < iLength; i++)
			{
				arr2DCopyPtr[i] = new T[jLength];

				for (int j = 0; j < jLength; j++)
				{
					arr2DCopyPtr[i][j] = arr2DPtr[i][j];
				}
			}

			return arr2DCopyPtr;
		}

Prosze o ich ocene i ewentualne wskazowki co do ulepszenia kodu.
Z gory dziekuje :)

5

Obie funkcje różnią się tylko jedną linijką. (DRY)

Nie chcę być złośliwy, ale w C++ kopiowanie wielowymiarowej tablicy(std::vector czy std::array) wygląda tak:

secondArray = firstArray;
0
tajny_agent napisał(a):

Obie funkcje różnią się tylko jedną linijką. (DRY)

  1. W pierwszym przypadku potrzebuje jedynie pustej zainicjowanej tablicy do ktorej moge wpisac w petli wartosci odczytywane z pliku
tajny_agent napisał(a):

Nie chcę być złośliwy, ale w C++ kopiowanie wielowymiarowej tablicy(std::vector czy std::array) wygląda tak:

secondArray = firstArray;
  1. Alez bron Cie Panie Boze :). W drugim przypadku chodzi o to ze kopiowana tablica (arr2DPtr) w zalozeniu jest lokalna w ciele pewnej metody klasy, natomiast kopiujaca (arr2DCopyPtr) jest tej samej klasy atrybutem. I tutaj jest problem bo w przypadku kopiowania o ktorym mowisz oba wskazniki sa identyczne. Kiedy metoda konczy swoje dzialanie szlak trafia tablice arr2DPtr co (o ile dobrze rozumiem zawilosci w kwestii wskaznikow w C++) ma fatalne konsekwencje dla arr2DCopyPtr. Zatem w skrocie chodzilo mi o skopiowanie wartosci z jednej tablicy do drugiej a nie jej klonowanie w przypadku kiedy obie maja rozny zasieg.
    Oczywiscie jest wysoce prawdopodobne, ze moge nie brac pewnych istotnych rzeczy pod uwage (c++ to nie moja bajka, jestem php-owcem), ale wg mojego aktualnego (niewielkiego zreszta) stanu wiedzy wlasnie w taki sposob moge sobie poradzic.

Jesli masz jakies sugestie mogac mi pomoc rozwiazac te zagwozdke - bede szczerze wdzieczny.

3
#include <iostream>
#include <vector>
#include <string>
#include <algorithm>

using Vec2Int = std::vector<std::vector<int>>;

template <typename C>
void Copy(const C& src, C& dest) {
   dest = src;
}

template <typename C>
void LocalCopy(C& coll) {
   C loclaColl{ {11, 22, 33}, {44, 55, 66} };
   Copy(loclaColl, coll);
}

template <typename C>
void Mult(C& coll, int n) {
   for (auto& dimension : coll) {
      std::transform(
         dimension.cbegin(), dimension.cend(), 
         dimension.begin(), 
         [=](auto& el) { return el * n; });
   }
}

template <typename C>
void Print(const C& coll, const std::string& opt = "") {
   if (!opt.empty()) { std::cout << opt << &coll << '\n'; }
   for (auto const& firstDimension : coll) {
      for (auto const& secondDimensionElement : firstDimension) {
         std::cout << secondDimensionElement << ' ';
      }
      std::cout << '\n';
   }
   std::cout << std::endl;
}

int main() {
   Vec2Int arr1{ { 1, 2, 3 }, {4, 5, 6 } };
   Vec2Int arr2;
   Vec2Int arr3;
   Copy(arr1, arr2);
   Print(arr1, "arr1");
   Mult(arr2, 3);
   Print(arr2, "arr2");
   LocalCopy(arr3);
   Mult(arr3, 2);
   Print(arr3, "arr3");
}

Czy o to chodziło?
https://wandbox.org/permlink/qsQGvyLXsVDZxfRH

Można też użyć jednowymiarowej tablicy z widokiem dwuwymiarowym.

1

A samo std::copy sobie tu nie da sobie rady?
Dobra rada tak ogólnie: wyrzuć książki/tutoriale do C++, których używasz.i zapomnij, że nie new i delete w ogóle istnieją.

2

Ten memset w przypadku bliżej nieokreślonego typu T jest błędem (zresztą źle go używasz). Jeśli T może być czymś więcej niż typem liczbowym, zastanowiłbym się nad użyciem std::allocator.

PS. ten sposób tworzenia tablicy 2D nie jest zbyt optymalny pamięciowo.

0

Panowie,

Wszystkim bardzo dziekuje za konstruktywne wypowiedzi.

1
Constantic napisał(a):
  1. Alez bron Cie Panie Boze :). W drugim przypadku chodzi o to ze kopiowana tablica (arr2DPtr) w zalozeniu jest lokalna w ciele pewnej metody klasy, natomiast kopiujaca (arr2DCopyPtr) jest tej samej klasy atrybutem. I tutaj jest problem bo w przypadku kopiowania o ktorym mowisz oba wskazniki sa identyczne. Kiedy metoda konczy swoje dzialanie szlak trafia tablice arr2DPtr co (o ile dobrze rozumiem zawilosci w kwestii wskaznikow w C++) ma fatalne konsekwencje dla arr2DCopyPtr. Zatem w skrocie chodzilo mi o skopiowanie wartosci z jednej tablicy do drugiej a nie jej klonowanie w przypadku kiedy obie maja rozny zasieg.
    Oczywiscie jest wysoce prawdopodobne, ze moge nie brac pewnych istotnych rzeczy pod uwage (c++ to nie moja bajka, jestem php-owcem), ale wg mojego aktualnego (niewielkiego zreszta) stanu wiedzy wlasnie w taki sposob moge sobie poradzic.

W kwestii wskaźników oczywiście masz rację. Kopiowanie wskaźnika nie powoduje skopiowania wszystkiego co się pod nim tak naprawdę kryje(o to właśnie chodzi we wskaźnikach :P).
Dlatego zasugerowałem użycie klasy std:vector lub std:array. Nie musisz sobie zawracać głowy alokowaniem/zwalnianiem pamięci, kopiowanie odbywa się za pomocą zwykłego operatora przypisania = i dodatkowo oferują kilka pomocnych funkcji. Prostsze jest również ich użycie w funkcjach z <algorithm>.

0

Panowie jesli mozna pojsc za ciosem to mam pytanie w zwiazku z "vector".

Otoz w jednym z naglowkow aplikacji "zapodalem" sobie taka definicje typu:

//ArrayHelper.h
//...
typedef std::vector<std::vector<string>> ArrString2D;

namespace CoreHelper
{
	class ArrayHelper
	{
	//...
	}
}

Zakladajac, iz w ten sposob bedzie ona dostepna globalnie - i tutaj pierwsze pytanie - czy praktyczniej nie bedzie umiescic definicji od razu w czasie "odpalenia" aplikacji ?
Ok, ale przejdzmy do meritum.
W innym miejscu chce wykorzystac zdefiniowany przez siebie typ w przypadku lokalnej tablicy, ale w ponizszym przypadku

//DataReader.cpp

ArrString2D DataReader::readFromFile(string file)
{
	ArrString2D arrString2D(*rowsNumberPtr, vector<string>(*colsNumberPtr));
}

Na gwiazdce "*rowsNumberPtr" wywala mi blad "no istance of constructor...". Sprobowalem zatem czegos takiego:

//DataReader.cpp

ArrString2D DataReader::readFromFile(string file)
{
	ArrString2D arrString2D(*rowsNumberPtr);
       //...
	while (!reader.eof()) {
       //...
		vector<string> col(*colsNumberPtr);
       //...
		while ((pos = line.find(delimiter)) != string::npos) {
       //...
			col.at(j) = line.substr(0, pos);
       //...
		}
       //...
		arrString2D.at(i).resize(*colsNumberPtr);
		arrString2D.at(i) = col;
       //...
	}
       //...        
}

Ale w momencie przypisania: "arrString2D.at(i) = col;" pojawia sie kolejny blad : "no operator "=" matches these operands".

Jesli jednak zrobie cos takiego:

//DataReader.cpp

ArrString2D DataReader::readFromFile(string file)
{
	vector<vector<string>> arrString2D(*rowsNumberPtr, vector<string> (*rowsNumberPtr));
}

Przypisanie dziala bez zarzutu, ale typ "vector<vector<string>>" deklarowany w ciele metody nie jest tozsamy z "typedef std::vector<std::vector<string>> ArrString2D;" :/

W jaki zatem sposob powinienem postapic ?
Z gory dziekuje za sugestie.

1

przejże sobie dokładnie jak wrócę z pracy ale:

  • using zamiast typedef
ArrString2D arrString2D(*rowsNumberPtr, vector<string>(*colsNumberPtr));

Co ty chcesz tu zrobić? masz typ vector<vector<>> i skąd weźmiesz taki konstruktor? Po prostu zrób tak

ArrString2D arrString2D;
// rezerwacja pamięci i dodawanie wektorów przez emplace_back itd. czy jakieś inne listy inicjalizacyjne. sprawdź dokumentacje

http://en.cppreference.com/w/cpp/container/vector

at() rzuca wyjątkami jak pamiętam do tego:
https://stackoverflow.com/questions/9912223/c-fill-a-vector-with-another-vector

0
revcorey napisał(a):
ArrString2D arrString2D;
// rezerwacja pamięci i dodawanie wektorów przez emplace_back itd. czy jakieś inne listy inicjalizacyjne. sprawdź dokumentacje

Works like a charm :D. Wczesniej probowalem identycznej deklaracji ale "sypalo" mi sie na push_back'u wiec nawet nie probowalem z emplace_back, a jednak okazalo sie, ze powinienem byl.

Bardzo Ci dziekuje.

0

ogólnie mógłbys przedstawić zamysł Bo mi się coś tu nie podoba. Szczególnie te pointery na int.

1
Constantic napisał(a):

Przypisanie dziala bez zarzutu, ale typ "vector<vector<string>>" deklarowany w ciele metody nie jest tozsamy z "typedef std::vector<std::vector<string>> ArrString2D;" :/

Nie masz gdzieś przypadkiem użyte using namespace std?

revcorey napisał(a):
ArrString2D arrString2D(*rowsNumberPtr, vector<string>(*colsNumberPtr));

Co ty chcesz tu zrobić? masz typ vector<vector<>> i skąd weźmiesz taki konstruktor? Po prostu zrób tak

A co jest z tym nie tak? test

0
revcorey napisał(a):

ogólnie mógłbys przedstawić zamysł Bo mi się coś tu nie podoba. Szczególnie te pointery na int.

Hmm, zamysl jest taki, ze utworzylem sobie klase do poboru danych konfiguracyjnych roznego typu. Ze wzgledu na poprzednia wersje tablic, klasa ta ma osobna metode do odczytu wymiarow tablic oraz osobna metode to ich odczytu i zapisu w postaci tablicy string (potem te tablice sa konwertowane na odpowiednie typy docelowe w klasach obslugujacych wlasciwe sobie zestawy konfiguracyjne).

Jesli chodzi o te pointery parametrach integer to odnosza sie one do atrybutow wymiarow tablicy tej samej klasy do ktorej nalezy metoda. Oczywiscie na pierwszy rzut oka wydaje sie zbedne przekazywanie atrybotow klasy do metody ktora rowniez do tej samej klasy nalezy - nie jestem jednak pewien czy nie bede uzywal tej funkcji w przypadku danych innych niz konfiguracyjne zatem postanowilem pozostawic ja w postaci odrobine bardziej uniwersalnej. Nadal pozostaje kwestia pointerow - chyba dlatego zeby miec pewnosc ze za kazdym razem odwoluje sie do odpowiednich wartosci jednak co tu duzo mowic - jeszcze troche kodu "uplynie" zanim tak naprawde zaczne nimi swobodnie operowac.

0
tajny_agent napisał(a):
Constantic napisał(a):

Przypisanie dziala bez zarzutu, ale typ "vector<vector<string>>" deklarowany w ciele metody nie jest tozsamy z "typedef std::vector<std::vector<string>> ArrString2D;" :/

Nie masz gdzieś przypadkiem użyte using namespace std?

No wlasnie o to chodzi, ze tak. Dzieki temu kilka innych deklaracji method zamiast "std::string" strkocilem do "string". Jak sie domyslam nie zadales tego pytania ot tak sobie i mozliwe, ze masz w zwiazku z tym cos do dodania :/ ...

0

Zainteresował mnie ten typedef i niekonsekwencja z przedrostkiem std

typedef std::vector<std::vector<string>> ArrString2D;

Ale w sumie VS bez problemu mi taki misz-masz przepuścił, więc mój strzał raczej chybiony ;)

0

Hmm, zamysl jest taki, ze utworzylem sobie klase do poboru danych konfiguracyjnych roznego typu.

czy możemy użyć std::map w w stylu std::map<string,string> gdzie jedno to parametr drugie wartość albo nawet std::vector<std::pair<string,string>> albo nawet std::tuple(jest jeszcze std::variant w c++17) w ostatecznej ostateczności jeśli jest taka potrzeba? Przejrzyj to.
Więcej później bo jestem zajęty. :)

0

esli chodzi o te pointery parametrach integer to odnosza sie one do atrybutow wymiarow tablicy tej samej klasy do ktorej nalezy metoda. Oczywiscie na pierwszy rzut oka wydaje sie zbedne przekazywanie atrybotow klasy do metody ktora rowniez do tej samej klasy nalezy - nie jestem jednak pewien czy nie bede uzywal tej funkcji w przypadku danych innych niz konfiguracyjne zatem postanowilem pozostawic ja w postaci odrobine bardziej uniwersalnej. Nadal pozostaje kwestia pointerow - chyba dlatego zeby miec pewnosc ze za kazdym razem odwoluje sie do odpowiednich wartosci jednak co tu duzo mowic - jeszcze troche kodu "uplynie" zanim tak

std::vector rozszerza się automatycznie. Oczywiście jak uprzednio zarezerwujesz pamięć to może nie być albo może być mniej alokacji później.Proste prymitywne typy jak int czyli tu rozmiar tablicy/vectora przekazuj przez wartość, taki zapis foo(int size) zostanie zoptymalizowany przez kompilator(użycie referencji mogło by tu sprawę pogorszyć, jeśli idzie o proste typy). A jak już tak chcesz przekazać takie rzeczy przez wskaźnik czy referencje pamiętaj o const.

No wlasnie o to chodzi, ze tak. Dzieki temu kilka innych deklaracji method zamiast "std::string" strkocilem do "string". Jak sie domyslam nie zadales tego pytania ot tak sobie i mozliwe, ze masz w zwiazku z tym cos do dodania :/ ...

Kolizja nazw np. jak będziesz chciał użyć innej biblioteki stringów?

0
revcorey napisał(a):

std::vector rozszerza się automatycznie. Oczywiście jak uprzednio zarezerwujesz pamięć to może nie być albo może być mniej alokacji później.

Wlasnie ze wzgledu na te koniecznsc wielokrotnych alokacji zdecydowalem sie najpierw szybciutko odczytac rozmiary tablicy, a nastepnie zadeklarowac ja jednznacznie (dynamicznie) w celu przypisania danych. Fakt to podwojna robota, ale pliki konfiguracyjne do obslugi sa malenkie i mam ich ok 7 sztuk.

revcorey napisał(a):

Proste prymitywne typy jak int czyli tu rozmiar tablicy/vectora przekazuj przez wartość, taki zapis foo(int size) zostanie zoptymalizowany przez kompilator(użycie referencji mogło by tu sprawę pogorszyć, >jeśli idzie o proste typy). A jak już tak chcesz przekazać takie rzeczy przez wskaźnik czy referencje pamiętaj o const.

Tu mnie masz, tego nie bylem swiadomy, dzieki.

revcorey napisał(a):

Kolizja nazw np. jak będziesz chciał użyć innej biblioteki stringów?

To tez prawda, chociaz w moim przypadku a zwlaszcza w przypadku aplikacji ktora pisze to raczej malo prawdopodobne - jesli chodzi o programowanie jestem "prymitywista" :) z wyboru i za wszelka cene staram sie opierach na jak najbardziej podstawowych rozwiazaniach.

Bardzo Ci dziekuje za wskazowki i sugestie.

0

lasnie ze wzgledu na te koniecznsc wielokrotnych alokacji zdecydowalem sie najpierw szybciutko odczytac rozmiary tablicy, a nastepnie zadeklarowac ja jednznacznie (dynamicznie) w celu przypisania danych. Fakt to podwojna robota, ale pliki konfiguracyjne do obslugi sa malenkie i mam ich ok 7 sztuk.

To jest przerost formy nad treścią według mnie ze względu że to będą małe vectory, nie ma w standardzie tego określonego ale zazwyczaj startowe capacity to 0, tyle że później vector potrafi sobie na zapas zaalokować aby uniknąć zbyt wielu odwołań menadżera pamięci.

To tez prawda, chociaz w moim przypadku a zwlaszcza w przypadku aplikacji ktora pisze to raczej malo prawdopodobne - jesli chodzi o programowanie jestem "prymitywista" :) z wyboru i za wszelka cene staram sie opierach na jak najbardziej podstawowych rozwiazaniach.

To jest zajebisty błąd. Po to porobiono te mapy, vectory i inne w std czy w języku jakieś r-wartości żeby z tego korzystać. Te kontenery czy algorytmy std też są pisane tak żeby być dość wydajne, oczywiście nie zawsze to będzie najwydajniejsze rozwiązanie ze względu na mnogość sytuacji. Co więcej jak zaczniesz budować jakieś swoje własne cuda możesz się pomylić albo porobi się takie spageti że spadnie ci wydajność. jechanie na dziś dzień na gołych wskaźnikach bez wyraźnego powodu to błąd. C++ to nie java script i frontend że co byś na tym nie napisał będzie mulić.

edit:
Polecam ci lekturę clean code.
poczytaj też o clang-tidy.

1

Bezpiecznie jest założyć, że w temacie struktur danych typu wektor, hashmapa itd. kompilator wie co robi. Ba, bezpiecznie jest założyć, że sami nie wiemy. Na CodeDive w 2015 bodajże ktoś robił zestawienie memset vs std::fill vs pętla i wyszło, że nawet dla małego embedded typu AVR STL potrafi być o parę procent szybszy jeżeli ten kod ma być czytelny. Czyli jak zwykle: „(jeszcze) nie optymalizuj”. Z mojego doświadczenia: raz wygrałem z kompilatorem walkę „C kontra C++”, tzn przepisanie wszystkiego w C pomogło, ale i tak bez dodatkowych flag kompilacji i atrybutów funkcji się nie obeszło. Ugraliśmy jedną czy dwie instrukcje asemblerowe, czyli w sumie może 8 bajtów objętości kodu (pewnie mniej, już nie pamiętam) w stosunku do 1kB całości. Tyle, że to było gotowe urządzenie z ATTiny13 z flashem 1kB zajętym w 95% i z całą odpowiedzialnością pisałem kod tak, żeby się zmieścił, nie żeby był czytelny. Piszesz na takie urządzenia? Nawet jeśli, to zmierz, potem optymalizuj, inaczej tylko powtarzasz mitologię.

1 użytkowników online, w tym zalogowanych: 0, gości: 1